Generics em Kotlin: para que servem e como usar

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 a Generics em Kotlin



Generics (generalizações) — um dos recursos-chave da linguagem Kotlin, que permite escrever código flexível, type-safe e reutilizável. Se você já escreveu List<String> ou Map<Int, User>, você já usou genéricos, mesmo sem pensar nisso. Mas o que está por trás desses colchetes angulares e como extrair o máximo deles? Neste artigo, vamos analisar para que servem os Generics em Kotlin, como aplicá-los na prática e quais recursos avançados (variance, reified, where) ajudarão você a escrever um código ainda mais limpo e seguro.



Este artigo é destinado a desenvolvedores que já conhecem os fundamentos do Kotlin e desejam aprofundar seus conhecimentos. Vamos abordar tudo: desde a sintaxe básica até exemplos reais de código em produção.



Para que servem os Generics? O problema sem eles



Imagine que você precisa escrever uma função que recebe uma lista de quaisquer elementos e retorna o primeiro elemento. Sem genéricos, você escreveria assim:



fun getFirst(list: List<Any>): Any {    return list[0]}


Agora, se você passar List<String>, receberá Any como saída. Para trabalhar com isso como uma string, será necessário fazer uma conversão explícita de tipos:



val first = getFirst(listOf("Kotlin", "Java")) as Stringfirst.length // Perigoso! Pode lançar ClassCastException


Isso é inconveniente e inseguro. O erro só aparecerá em tempo de execução. Generics resolvem esse problema, preservando a informação do tipo em tempo de compilação.



Principais vantagens dos Generics:

  • Type safety — o compilador verifica os tipos em tempo de compilação, não em tempo de execução.
  • Eliminação de conversões explícitas — o código fica mais limpo e legível.
  • Reutilização — uma classe ou função funciona com diferentes tipos.


Sintaxe básica: classes, funções e restrições



Classes genéricas



A maneira mais simples de começar é declarar uma classe com um ou mais parâmetros de tipo. Os parâmetros de tipo são indicados entre colchetes angulares <T> (a letra pode ser qualquer uma, mas tradicionalmente usam T, E, K, V):



class Box<T>(val value: T)

fun main() { val stringBox = Box(\"Kotlin\") // O tipo é inferido automaticamente val intBox = Box(42) // Box<Int>

println(stringBox.value.length) // Funciona sem conversão! println(intBox.value + 1) // 43}


Kotlin consegue inferir o tipo, portanto não é necessário escrever Box<String> — basta Box(\"Kotlin\").



Funções genéricas



Funções também podem ser genéricas. O parâmetro de tipo é indicado antes do nome da função:



fun <T> getMiddleElement(list: List<T>): T? {    if (list.isEmpty()) return null    return list[list.size / 2]}

fun main() { val numbers = listOf(1, 2, 3, 4, 5) val middle = getMiddleElement(numbers) // O tipo Int? é inferido automaticamente println(middle) // 3}


Restrições (where / upper bound)



Às vezes é necessário limitar quais tipos podem ser usados em um genérico. Por exemplo, para chamar o método .compareTo(), o tipo deve implementar a interface Comparable:



fun <T : Comparable<T>> maxOfTwo(a: T, b: T): T {    return if (a >= b) a else b}

fun main() { println(maxOfTwo(10, 20)) // 20 println(maxOfTwo(\"A\", \"B\")) // B // maxOfTwo(10, \"A\") // Erro de compilação: tipos não coincidem}


Se forem necessárias várias restrições, use a palavra-chave where:



interface Printable {    fun print()}

fun <T> process(item: T) where T : Comparable<T>, T : Printable

Blogs

Book Recommendations