Generics em Kotlin: como deixar de ter medo de colchetes angulares e escrever código limpo

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: por que generics não são assustadores, mas sim convenientes

\n\n

Imagine que você está escrevendo uma classe "Caixa". Nela você pode colocar qualquer coisa: um livro, uma maçã, um telefone. Mas se você a fizer para Object, toda vez que for retirar algo terá que fazer as? Book e torcer para não cair em um ClassCastException. Familiar?

\n\n

É aqui que os generics entram em cena — um poderoso mecanismo de tipagem em Kotlin que permite escrever código flexível sem sacrificar a segurança de tipos. Neste artigo, vamos explorar generics em Kotlin do simples ao complexo: classes básicas, funções, variância (in/out), tipos reificados e star projections. Você aprenderá como tornar o código mais limpo e o compilador seu maior aliado.

\n\n

Este artigo foi preparado para o portal educacional pythonlib.ru — seu guia para o mundo das linguagens de programação modernas.

\n\n

1. Fundamentos: o que são generics em Kotlin?

\n\n

Generics (tipos genéricos) permitem parametrizar classes, interfaces e funções com um tipo que será especificado no momento do uso. Isso elimina a duplicação de código e fornece controle sobre os tipos em tempo de compilação.

\n\n

Veja um exemplo simples:

\n\n
class Box<T>(val item: T)\n\nfun main() {\n    val bookBox = Box<String>("Kotlin in Action")\n    val intBox = Box<Int>(42)\n    // O compilador sabe o tipo!\n    val title: String = bookBox.item\n}
\n\n

Pontos-chave:

\n
    \n
  • <T> — parâmetro de tipo (pode ser qualquer letra, mas por convenção T, R, E, K, V).
  • \n
  • O tipo é fixado ao criar o objeto — sem casts em tempo de execução.
  • \n
  • Generics funcionam apenas em tempo de compilação (type erasure), mas Kotlin adiciona "reificação" (mais sobre isso adiante).
  • \n
\n\n

Funções genéricas

\n

A tipagem em Kotlin também funciona para funções. Por exemplo, uma função que retorna o primeiro elemento de uma lista:

\n\n
fun <T> firstItem(list: List<T>): T? = list.firstOrNull()\n\nfun main() {\n    val numbers = listOf(1, 2, 3)\n    val first: Int? = firstItem(numbers) // T = Int\n    println(first) // 1\n}
\n\n

Observe: Kotlin consegue inferir o tipo a partir do contexto — não é necessário escrever firstItem<Int>(numbers).

\n\n

2. Variância: in e out — como não quebrar a segurança de tipos

\n\n

O tópico mais difícil para iniciantes é a variância. Ela responde à pergunta: pode List<String> ser um subtipo de List<Any>?

\n\n

Em Java, isso é resolvido com wildcards (? extends / ? super). Em Kotlin, com out e in.

\n\n

out (covariância) — apenas leitura

\n

A marcação out significa que o tipo genérico só pode ser "fornecido" (retornado de métodos). Exemplo — List<out T>.

\n\n
interface Source<out T> {\n    fun next(): T  // OK: T na saída\n    // fun add(item: T) // Erro! T na entrada é proibido\n}\n\nfun demo(source: Source<String>) {\n    val anySource: Source<Any> = source // out permite isso\n}
\n\n

Por que isso é seguro? Se só podemos ler String da fonte, então ela pode ser considerada uma fonte de Any.

\n\n

in (contravariância) — apenas escrita

\n

in — ao contrário: o tipo só pode estar na entrada. Clássico — Comparable.

\n\n
interface Comparable<in T> {\n    operator fun compareTo(other: T): Int\n}\n\nfun demo(comp: Comparable<Number>) {\n    val intComp: Comparable<Int> = comp // in permite isso\n}
\n\n

Se Comparable<Number> consegue comparar qualquer número, então certamente conseguirá comparar Int.

\n\n

in out (invariância) — padrão

\n

Se nem in nem out forem especificados, o tipo é inv

Blogs

Book Recommendations