Generics em Kotlin: para que servem e como usar na prática
Generics (genéricos) — um dos recursos-chave da linguagem Kotlin, que permite escrever código mais flexível, type-safe e reutilizável. Se você já escreveu List<String> ou Map<Int, User>, já encontrou genéricos. Mas para que eles realmente servem e como extrair o máximo deles? Neste artigo, vamos analisar a teoria e passar para a prática com exemplos reais.
O problema que os Generics resolvem
Imagine que você precisa escrever uma função que retorna o primeiro elemento de uma lista. Sem genéricos, você teria que escrever uma função separada para cada tipo de dado:
fun getFirstString(list: List<String>): String? = list.firstOrNull()fun getFirstInt(list: List<Int>): Int? = list.firstOrNull()fun getFirstUser(list: List<User>): User? = list.firstOrNull()Isso leva à duplicação de código. Usar Any resolveria o problema, mas você perderia a informação do tipo em tempo de compilação:
fun getFirst(list: List<Any>): Any? = list.firstOrNull()
// Seria necessário fazer casting de tiposval name: String? = getFirst(strings) as? StringOs Generics resolvem esse problema de forma elegante:
fun <T> getFirst(list: List<T>): T? = list.firstOrNull()
// O compilador sabe o tipo!val name: String? = getFirst(listOf("Alice", "Bob"))val age: Int? = getFirst(listOf(25, 30))Agora uma função funciona com qualquer tipo, mantendo total type-safety.
Fundamentos dos Generics em Kotlin: sintaxe e declaração
Genéricos são declarados usando colchetes angulares <T>. A letra T é apenas uma convenção; você pode usar qualquer nome: E (elemento), K (chave), V (valor).
Classes Genéricas
class Box<T>(val value: T) { fun getContent(): T = value}
fun main() { val stringBox = Box("Hello") val intBox = Box(42) println(stringBox.getContent()) // Hello println(intBox.getContent()) // 42}Funções Genéricas
fun <T> printItem(item: T) { println("Item: $item")}
// Pode-se especificar o tipo explicitamenteprintItem<String>("Kotlin")// Ou confiar na inferência de tiposprintItem(100)Interfaces Genéricas
interface Repository<T> { fun getById(id: Int): T fun save(item: T): Boolean}
class UserRepository : Repository<User> { override fun getById(id: Int): User { /* ... */ } override fun save(item: User): Boolean { /* ... */ }}Variância: in, out e asterisco *
Um dos recursos mais poderosos do Kotlin é a variância (variance). Ela responde à pergunta: pode List<String> ser um subtipo de List<Any>?
Covariância (out)
Use out quando seu tipo genérico é apenas retornado (producer). Por exemplo, List<T> é covariante:
// Você pode atribuir List<String> a uma variável List<Any>val strings: List<String> = listOf("a", "b")val anyList: List<Any> = strings // OK!
// Mas não pode adicionar elementos// anyList.add(1) // Erro de compilação!Exemplo próprio:
class Producer<out T>(private val value: T) { fun produce(): T = value}
fun main() { val stringProducer: Producer<String> = Producer("Hello") val anyProducer: Producer<Any> = stringProducer // Funciona! println(anyProducer.produce())}Contravariância (in)
Use in quando o tipo é apenas recebido (consumer). Por exemplo, Comparable<T>:
Blogs
Book Recommendations
Leia também
Cliente HTTP em JavaScript: fazendo requisições corretamente no Node.js
Trabalhando com datas e horas em TypeScript: guia completo
Padrões de Projeto em C++: Guia Completo com Exemplos
Injeção de Dependência em PHP: Guia Completo com Exemplos
Fluxo de trabalho Git para desenvolvedor PHP: do caos ao código limpo