Introdução aos Generics em Kotlin
\n\nAo desenvolver em Kotlin, mais cedo ou mais tarde você se deparará com uma situação em que o mesmo algoritmo ou estrutura de dados precisa funcionar com diferentes tipos de objetos. Imagine que você está escrevendo uma função para encontrar o elemento máximo em uma lista — ela deve lidar igualmente bem tanto com uma lista de números quanto com uma lista de strings. Sem ferramentas especiais, você teria que escrever implementações separadas para cada tipo. É aqui que os Generics (tipos genéricos ou generalizações) entram em ação.
\n\nGenerics em Kotlin é um mecanismo poderoso que permite criar classes, funções e interfaces que funcionam com diferentes tipos de dados, mantendo a tipagem estrita em tempo de compilação. Neste artigo, vamos entender para que servem os Generics e aprender a usá-los com exemplos práticos.
\n\nO que são Generics e para que servem?
\n\nImagine uma caixa que pode guardar qualquer objeto: um livro, um telefone ou uma maçã. Na programação, essa "caixa" é uma classe ou função genérica. Sem Generics, você teria que usar o tipo Any (análogo ao Object em Java) e fazer conversões de tipo constantemente, o que é propenso a erros.
O problema sem Generics
\n\nfun <T> printList(list: List<T>) {\n for (item in list) {\n println(item)\n }\n}\n\n// Sem Generics, teria que ser escrito assim:\nfun printListAny(list: List<Any>) {\n for (item in list) {\n // É necessário verificar o tipo e converter\n if (item is String) {\n println("String: $item")\n } else if (item is Int) {\n println("Número: $item")\n }\n }\n}\n\nVantagens do uso de Generics
\n\n- \n
- Segurança de tipos — o compilador verifica a correta utilização dos tipos em tempo de compilação. \n
- Reutilização de código — uma função ou classe funciona com qualquer tipo. \n
- Eliminação de conversões explícitas — o código fica mais limpo e menos sujeito a erros. \n
- Desempenho — não há custos adicionais com verificação de tipos em tempo de execução. \n
Principais construções de Generics em Kotlin
\n\n1. Funções Genéricas
\n\nUma função genérica é uma função que aceita um parâmetro de tipo. Ela é declarada usando colchetes angulares <T> antes do nome da função.
fun <T> getFirstElement(list: List<T>): T? {\n return list.firstOrNull()\n}\n\nfun main() {\n val numbers = listOf(1, 2, 3, 4, 5)\n val firstNumber = getFirstElement(numbers)\n println("Primeiro número: $firstNumber") // 1\n\n val words = listOf("Gato", "Cachorro", "Papagaio")\n val firstWord = getFirstElement(words)\n println("Primeira palavra: $firstWord") // Gato\n}\n\nObserve: o tipo T é inferido automaticamente pelo compilador. Você pode especificá-lo explicitamente, mas isso não é obrigatório:
val firstNumber = getFirstElement<Int>(numbers)\n\n2. Classes Genéricas
\n\nUma classe genérica é uma classe que pode armazenar e manipular objetos de qualquer tipo. Um excelente exemplo é a classe padrão Box.
class Box<T>(private val content: T) {\n fun getContent(): T {\n return content\n }\n\n fun isTypeOf(typeClass: Class<*>): Boolean {\n return typeClass.isInstance(content)\n }\n}\n\nfun main() {\n val stringBox = Box("Olá, mundo!")\n println(stringBox.getContent()) // Olá, mundo!\n\n val intBox = Box(42)\n println(intBox.getContent()) // 42\n\n // Verificação de tipo\n println(intBox.isTypeOf(Int::class.java)) // true\n}\n\n3. Interfaces Genéricas
\n\nInterfaces também podem ser genéricas. Isso é especialmente útil ao criar coleções ou contratos para processamento de dados.
\n\ninterface Repository<T> {\n