---
title: "Generics em Kotlin Tutorial em Português — Passo a Passo | Kotlin Brasil"
url: "https://kotlin.dev.br/tutoriais/generics-kotlin/"
markdown_url: "https://kotlin.dev.br/tutoriais/generics-kotlin.MD"
description: "Aprenda Generics em Kotlin com exemplos práticos: type parameters, constraints, variance in/out, star projection e reified neste tutorial completo."
date: "2025-06-28"
author: "Karina Melo"
---

# Generics em Kotlin Tutorial em Português — Passo a Passo | Kotlin Brasil

Aprenda Generics em Kotlin com exemplos práticos: type parameters, constraints, variance in/out, star projection e reified neste tutorial completo.


Neste tutorial, você vai aprender tudo sobre **Generics em Kotlin** — um recurso fundamental para escrever código reutilizável e type-safe. Generics permitem criar classes, interfaces e funções que operam com diferentes tipos sem sacrificar a segurança de tipos do compilador. Ao final, você vai dominar type parameters, constraints, variance com `in`/`out`, star projection e reified type parameters.

## O que São Generics?

[Generics](/glossario/generics/) permitem que você escreva código que funciona com qualquer tipo, enquanto ainda mantém a verificação de tipos em tempo de compilação. Sem generics, você teria que usar `Any` e fazer casts manuais, perdendo a segurança de tipos.

Você já usa generics diariamente, mesmo sem perceber. Toda vez que declara uma `List<String>`, `Map<String, Int>` ou `MutableList<Usuario>`, está usando generics.

## Type Parameters Básicos

A sintaxe básica de generics usa colchetes angulares `<T>`, onde `T` é o type parameter — um "placeholder" para o tipo real que será usado:

```kotlin
// Classe genérica
class Caixa<T>(val conteudo: T) {
    fun abrir(): T = conteudo

    fun transformar(bloco: (T) -> String): String {
        return bloco(conteudo)
    }
}

fun main() {
    val caixaTexto = Caixa("Kotlin Brasil")
    println(caixaTexto.abrir()) // Kotlin Brasil

    val caixaNumero = Caixa(42)
    println(caixaNumero.abrir()) // 42

    // O tipo é inferido automaticamente
    val caixaLista = Caixa(listOf(1, 2, 3))
    println(caixaLista.abrir()) // [1, 2, 3]

    // Transformando o conteúdo
    println(caixaNumero.transformar { "O numero é $it" })
}
```

Note que o compilador infere o tipo genérico automaticamente na maioria dos casos. Você não precisa escrever `Caixa<String>("Kotlin Brasil")` — o compilador deduz `String` a partir do argumento.

## Funções Genéricas

Funções também podem ser genéricas, independentemente de estarem dentro de uma classe genérica ou não:

```kotlin
fun <T> primeiroOuNull(lista: List<T>): T? {
    return if (lista.isNotEmpty()) lista[0] else null
}

fun <T> List<T>.trocar(indice1: Int, indice2: Int): List<T> {
    val resultado = this.toMutableList()
    val temp = resultado[indice1]
    resultado[indice1] = resultado[indice2]
    resultado[indice2] = temp
    return resultado
}

fun <K, V> mapearPares(pares: List<Pair<K, V>>): Map<K, V> {
    return pares.toMap()
}

fun main() {
    println(primeiroOuNull(listOf("a", "b", "c"))) // a
    println(primeiroOuNull(emptyList<Int>()))        // null

    val numeros = listOf(10, 20, 30, 40)
    println(numeros.trocar(0, 3)) // [40, 20, 30, 10]

    val mapa = mapearPares(listOf("nome" to "Ana", "cidade" to "SP"))
    println(mapa) // {nome=Ana, cidade=SP}
}
```

## Constraints (Upper Bounds)

Às vezes você quer restringir o tipo genérico para aceitar apenas subtipos de uma determinada classe ou interface. Isso é feito com **upper bounds**:

```kotlin
// T precisa ser Comparable
fun <T : Comparable<T>> maximo(a: T, b: T): T {
    return if (a > b) a else b
}

// T precisa implementar CharSequence
fun <T : CharSequence> contarCaracteres(texto: T): Int {
    return texto.length
}

fun main() {
    println(maximo(10, 20))       // 20
    println(maximo("abacaxi", "banana")) // banana
    println(contarCaracteres("Kotlin"))  // 6

    // Isto nao compila:
    // maximo(listOf(1), listOf(2)) — List nao é Comparable
}
```

Para múltiplas restrições, use a cláusula `where`:

```kotlin
fun <T> processar(item: T) where T : Comparable<T>, T : CharSequence {
    println("Valor: $item, Tamanho: ${item.length}")
    // T é tanto Comparable quanto CharSequence
}

fun main() {
    processar("Kotlin") // String implementa ambas
    // processar(42) — Int nao implementa CharSequence — ERRO!
}
```

## Variance: in e out

Variance é um dos conceitos mais importantes (e confusos) de generics. Ela define como a relação de herança entre tipos afeta tipos genéricos.

### Covariância com out (Producer)

Se `Dog` é subtipo de `Animal`, uma `List<Dog>` deveria ser subtipo de `List<Animal>`? Em Kotlin, sim — porque `List` é declarada com `out`:

```kotlin
// out = covariante = o tipo só SAI (é produzido)
interface Produtor<out T> {
    fun produzir(): T
    // fun consumir(item: T) — ERRO! Não pode usar T como parâmetro
}

class ProdutorDeString : Produtor<String> {
    override fun produzir(): String = "Hello Kotlin"
}

fun imprimirProduto(produtor: Produtor<Any>) {
    println(produtor.produzir())
}

fun main() {
    val produtorString: Produtor<String> = ProdutorDeString()

    // Funciona! Produtor<String> é subtipo de Produtor<Any>
    imprimirProduto(produtorString)
}
```

Use `out` quando o tipo genérico é apenas **produzido** (retornado), nunca consumido como parâmetro.

### Contravariância com in (Consumer)

O oposto: se `Dog` é subtipo de `Animal`, um `Comparator<Animal>` pode ser usado onde se espera `Comparator<Dog>`:

```kotlin
// in = contravariante = o tipo só ENTRA (é consumido)
interface Consumidor<in T> {
    fun consumir(item: T)
    // fun produzir(): T — ERRO! Não pode retornar T
}

class ConsumidorDeAny : Consumidor<Any> {
    override fun consumir(item: Any) {
        println("Consumindo: $item")
    }
}

fun alimentar(consumidor: Consumidor<String>) {
    consumidor.consumir("Kotlin")
}

fun main() {
    val consumidorAny: Consumidor<Any> = ConsumidorDeAny()

    // Funciona! Consumidor<Any> é subtipo de Consumidor<String>
    alimentar(consumidorAny)
}
```

### Resumo de Variance

```kotlin
// Pense assim:
// out T → Producer<out T> → T só aparece em posição de retorno
// in T  → Consumer<in T>  → T só aparece em posição de parâmetro

// Exemplos da stdlib:
// List<out E>           → só produz E (get, iterator)
// Comparable<in T>      → só consome T (compareTo recebe T)
// MutableList<E>        → invariante (produz E e consome E)
```

## Star Projection

Quando você não sabe ou não se importa com o tipo genérico, use a **star projection** `*`:

```kotlin
fun imprimirConteudo(lista: List<*>) {
    for (item in lista) {
        println(item) // item é do tipo Any?
    }
}

fun verificarTipo(caixa: Caixa<*>) {
    val conteudo = caixa.abrir() // tipo: Any?
    println("Conteúdo: $conteudo")
}

fun main() {
    imprimirConteudo(listOf(1, "dois", 3.0, true))

    val caixas: List<Caixa<*>> = listOf(
        Caixa("texto"),
        Caixa(42),
        Caixa(true)
    )

    caixas.forEach { verificarTipo(it) }
}
```

`List<*>` é equivalente a `List<out Any?>` — você pode ler itens como `Any?`, mas não pode adicionar itens (exceto `null`).

## Reified Type Parameters

Devido ao **type erasure** da JVM, informações sobre generics são apagadas em tempo de execução. Isso significa que você não pode fazer `is T` ou `T::class` normalmente. A solução é usar [reified](/glossario/reified/) com funções [inline](/glossario/inline/):

```kotlin
inline fun <reified T> verificarTipo(valor: Any): Boolean {
    return valor is T
}

inline fun <reified T> filtrarPorTipo(lista: List<Any>): List<T> {
    return lista.filterIsInstance<T>()
}

inline fun <reified T> nomeDoTipo(): String {
    return T::class.simpleName ?: "Desconhecido"
}

fun main() {
    println(verificarTipo<String>("texto"))  // true
    println(verificarTipo<Int>("texto"))     // false

    val misturada = listOf(1, "dois", 3, "quatro", 5.0)

    val strings = filtrarPorTipo<String>(misturada)
    println(strings) // [dois, quatro]

    val inteiros = filtrarPorTipo<Int>(misturada)
    println(inteiros) // [1, 3]

    println(nomeDoTipo<String>()) // String
    println(nomeDoTipo<List<Int>>()) // List
}
```

`reified` só funciona com funções `inline` porque o compilador substitui a chamada da função pelo seu corpo, inserindo o tipo real no lugar do type parameter.

## Type Erasure

Na JVM, informações de tipo genérico são apagadas durante a compilação. Isso tem implicações práticas:

```kotlin
fun main() {
    val listaStrings = listOf("a", "b")
    val listaInts = listOf(1, 2)

    // Em tempo de execucao, ambas são apenas "List"
    // Isto nao funciona:
    // if (listaStrings is List<String>) — ERRO: Cannot check for instance of erased type

    // Mas isto funciona com star projection:
    if (listaStrings is List<*>) {
        println("É uma lista!") // Funciona
    }

    // Para verificar o tipo dos elementos, use reified:
}

// Abordagem segura com reified
inline fun <reified T> List<*>.ehListaDe(): Boolean {
    return all { it is T }
}
```

## Exemplo Prático Completo

Vamos criar um repositório genérico que funciona com qualquer entidade:

```kotlin
interface Entidade {
    val id: Long
}

data class Usuario(override val id: Long, val nome: String, val email: String) : Entidade
data class Produto(override val id: Long, val nome: String, val preco: Double) : Entidade

class Repositorio<T : Entidade> {
    private val itens = mutableMapOf<Long, T>()

    fun salvar(item: T): T {
        itens[item.id] = item
        return item
    }

    fun buscarPorId(id: Long): T? = itens[id]

    fun listarTodos(): List<T> = itens.values.toList()

    fun remover(id: Long): Boolean = itens.remove(id) != null

    fun buscar(filtro: (T) -> Boolean): List<T> = itens.values.filter(filtro)

    val tamanho: Int get() = itens.size
}

fun main() {
    val usuarios = Repositorio<Usuario>()
    usuarios.salvar(Usuario(1, "Ana", "ana@email.com"))
    usuarios.salvar(Usuario(2, "Bruno", "bruno@email.com"))
    usuarios.salvar(Usuario(3, "Carla", "carla@email.com"))

    println("Total: ${usuarios.tamanho}")

    val encontrado = usuarios.buscarPorId(2)
    println("Encontrado: $encontrado")

    val comA = usuarios.buscar { it.nome.startsWith("A") || it.nome.startsWith("a") }
    println("Nomes com A: $comA")

    val produtos = Repositorio<Produto>()
    produtos.salvar(Produto(1, "Notebook", 3500.0))
    produtos.salvar(Produto(2, "Mouse", 89.90))

    val caros = produtos.buscar { it.preco > 100 }
    println("Produtos caros: $caros")
}
```

## Erros Comuns

1. **Esquecer type erasure**: tentar fazer `is List<String>` em tempo de execução não funciona. Use star projection (`is List<*>`) ou `reified` com funções `inline`.

2. **Confundir `in` e `out`**: lembre da regra mnemônica — **out** = Producer (o tipo sai), **in** = Consumer (o tipo entra). Se errar a variance, o compilador mostrará erros confusos sobre posições ilegais.

3. **Usar `reified` sem `inline`**: `reified` só funciona com funções `inline`. Sem inline, o tipo seria apagado e `reified` não teria efeito. O compilador não permitirá essa combinação inválida.

4. **Não definir upper bounds quando necessário**: se você chama métodos específicos de um tipo dentro de uma função genérica (como `compareTo`), precisa declarar o bound (`<T : Comparable<T>>`). Caso contrário, `T` é tratado como `Any?` e o método não estará disponível.

5. **Ignorar a nullability de type parameters**: por padrão, `<T>` pode ser nullable (`T` aceita `String?`). Se você precisa garantir que T nunca será null, use `<T : Any>` como upper bound.

## Conclusão e Próximos Passos

Generics são essenciais para escrever código Kotlin robusto e reutilizável. Neste tutorial, você aprendeu type parameters, constraints com upper bounds, variance com `in`/`out`, star projection, reified type parameters e type erasure. Esses conceitos formam a base para entender APIs complexas como as de coleções, coroutines e frameworks como Ktor e Spring.

Para continuar aprofundando, explore:

- [Sealed Classes](/tutoriais/sealed-classes-tutorial/) para hierarquias de tipos genéricos
- [Extension Functions](/tutoriais/extension-functions-tutorial/) genéricas
- [Lambdas](/tutoriais/lambdas-kotlin/) com tipos genéricos em higher-order functions
- [Coroutines](/tutoriais/coroutines-tutorial-básico/) que usam generics extensivamente (`Deferred<T>`, `Flow<T>`)

Pratique criando suas próprias classes e funções genéricas. Comece simples e vá adicionando constraints e variance conforme a necessidade. Com o tempo, generics se tornarão naturais no seu código Kotlin.
