O que são Generics em Kotlin?
Generics permitem criar classes, interfaces e funções que funcionam com qualquer tipo, mantendo a segurança de tipos em tempo de compilação. Em vez de escrever código específico para Int, String ou qualquer outro tipo, você escreve uma vez e reutiliza com qualquer um deles.
Classe genérica
class Caixa<T>(val conteudo: T) {
fun abrir(): T {
println("Abrindo a caixa...")
return conteudo
}
}
fun main() {
val caixaDeTexto = Caixa("Presente surpresa")
val caixaDeNumero = Caixa(42)
println(caixaDeTexto.abrir()) // Presente surpresa
println(caixaDeNumero.abrir()) // 42
}
O T é um parâmetro de tipo — pode ser qualquer coisa. O compilador garante que você não misture os tipos.
Função genérica
fun <T> trocar(par: Pair<T, T>): Pair<T, T> {
return Pair(par.second, par.first)
}
fun main() {
val original = Pair("Kotlin", "Java")
val trocado = trocar(original)
println(trocado) // (Java, Kotlin)
}
Restrições de tipo
Você pode restringir quais tipos são aceitos usando where ou ::
fun <T : Comparable<T>> maior(a: T, b: T): T {
return if (a > b) a else b
}
fun main() {
println(maior(10, 20)) // 20
println(maior("Kotlin", "Java")) // Kotlin
}
Quando precisa de múltiplas restrições, use a cláusula where:
fun <T> processar(item: T) where T : Comparable<T>, T : CharSequence {
println("Tamanho: ${item.length}, Valor: $item")
}
fun main() {
processar("Kotlin") // Tamanho: 6, Valor: Kotlin
// processar(42) // Erro: Int nao implementa CharSequence
}
Variância: in e out
Kotlin tem um sistema de variância mais explícito que Java:
// out = produtor (covariant) — só retorna T
interface Fonte<out T> {
fun obter(): T
}
// in = consumidor (contravariant) — só recebe T
interface Destino<in T> {
fun enviar(item: T)
}
class FonteDeString : Fonte<String> {
override fun obter() = "Kotlin Brasil"
}
fun main() {
val fonte: Fonte<Any> = FonteDeString() // Funciona por causa do out
println(fonte.obter())
}
out Tsignifica queTsó aparece como retorno (posição de saída)in Tsignifica queTsó aparece como parâmetro (posição de entrada)
Projeção de tipo (star projection)
Quando você não se importa com o tipo genérico, use *:
fun imprimirItens(lista: List<*>) {
lista.forEach { println(it) }
}
Reified type parameters
Em Kotlin, os tipos genéricos são apagados em tempo de execução (type erasure). Porém, com funções inline e o modificador reified, você pode acessar o tipo genérico em runtime:
inline fun <reified T> verificarTipo(valor: Any): Boolean {
return valor is T
}
fun main() {
println(verificarTipo<String>("Kotlin")) // true
println(verificarTipo<Int>("Kotlin")) // false
}
Isso é muito usado em frameworks para deserialização, injeção de dependência e navegação entre telas no Android.
Generics em interfaces e herança
Generics se combinam naturalmente com interfaces para criar contratos flexíveis:
interface Repositorio<T> {
fun buscarPorId(id: Int): T?
fun salvar(item: T)
fun listarTodos(): List<T>
}
data class Produto(val id: Int, val nome: String, val preco: Double)
class ProdutoRepositorio : Repositorio<Produto> {
private val itens = mutableListOf<Produto>()
override fun buscarPorId(id: Int) = itens.find { it.id == id }
override fun salvar(item: Produto) { itens.add(item) }
override fun listarTodos() = itens.toList()
}
fun main() {
val repo = ProdutoRepositorio()
repo.salvar(Produto(1, "Notebook", 3500.0))
repo.salvar(Produto(2, "Mouse", 89.90))
println(repo.listarTodos())
}
Casos de Uso no Mundo Real
- Repositórios genéricos: criar uma interface
Repositorio<T>que define operações CRUD e implementar para cada entidade do sistema, eliminando duplicação de código de acesso a dados. - Respostas de API padronizadas: usar uma classe
ApiResponse<T>que encapsula sucesso, erro e loading, permitindo que qualquer endpoint retorne o mesmo formato independente do tipo de dado. - Adapters no Android:
RecyclerView.Adapter<VH>usa generics para tipar o ViewHolder, e você pode criar adapters genéricos que funcionam com diferentes tipos de itens de lista. - Injeção de dependência: frameworks como Koin e Hilt usam
reifiedtype parameters para resolver dependências pelo tipo sem precisar passar a classe como parâmetro explícito.
Boas Práticas
- Use nomes de parâmetros de tipo descritivos quando o contexto exigir:
Tpara tipo genérico,KeVpara chave e valor,Epara elementos de coleção,Rpara tipo de retorno. - Prefira
outem interfaces que apenas produzem valores einem interfaces que apenas consomem. Isso torna a API mais flexível para os consumidores. - Utilize restrições de tipo (
T : AlgumaInterface) sempre que possível para evitar casts desnecessários e manter a segurança de tipos. - Combine
inlinecomreifiedquando precisar acessar o tipo genérico em runtime, em vez de passarKClass<T>como parâmetro. - Evite star projection (
*) quando você pode ser mais específico sobre o tipo, pois*limita as operações disponíveis.
Erros Comuns
- Tentar fazer
is Tsemreified: devido ao type erasure, verificar o tipo genérico em runtime exige que a função sejainlinee o parâmetro de tipo sejareified. - Confundir
ineout: usaroutquando o tipo aparece em posição de entrada (parâmetro) causa erro de compilação. Lembre:out= saída (retorno),in= entrada (parâmetro). - Criar hierarquias genéricas excessivamente complexas: aninhar múltiplos parâmetros de tipo como
Classe<A<B<C>>>dificulta a leitura. Simplifique com typealias ou quebre em interfaces menores. - Esquecer que
List<Any>não é supertipo deList<String>sem variância: em Kotlin,Listjá é declarada comoList<out E>, então funciona. Mas para suas próprias classes, você precisa declarar a variância explicitamente. - Usar casts genéricos não verificados:
lista as List<String>gera um warning de unchecked cast porque o tipo genérico é apagado em runtime. UsefilterIsInstance<String>()quando possível.
Perguntas Frequentes
Qual a diferença entre generics em Kotlin e em Java? Kotlin usa in e out para variância no local de declaração (declaration-site variance), enquanto Java usa wildcards ? extends e ? super no local de uso (use-site variance). Kotlin também suporta reified type parameters, que não existem em Java.
O que é type erasure e como afeta generics? Type erasure significa que os tipos genéricos são apagados em tempo de execução. Um List<String> e um List<Int> são indistinguíveis na JVM. O modificador reified em funções inline é a solução do Kotlin para contornar essa limitação.
Posso ter múltiplos parâmetros de tipo? Sim. Você pode declarar quantos precisar: class Mapa<K, V>, fun <A, B> converter(valor: A, transformador: (A) -> B): B. Cada parâmetro pode ter suas próprias restrições.
Quando devo usar star projection? Use * quando você precisa trabalhar com um tipo genérico mas não se importa com o parâmetro de tipo específico, como em funções que apenas imprimem ou verificam se uma coleção está vazia.
Termos Relacionados
- Interface — contratos que frequentemente utilizam generics para definir APIs flexíveis
- Inline — modificador necessário para usar
reifiedtype parameters - Fun — funções genéricas são declaradas com
funseguido de parâmetros de tipo - Lambda — frequentemente usada com funções genéricas como
map<T, R>
Generics são essenciais para escrever código reutilizável e type-safe. A biblioteca padrão do Kotlin usa generics em praticamente tudo: listas, maps, flows e muito mais.