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 (tipos genéricos) são um dos recursos principais de linguagens de programação estaticamente tipadas, incluindo Kotlin. Eles permitem escrever código flexível e reutilizável que funciona com diferentes tipos de dados, mantendo a segurança de tipos estrita em tempo de compilação.



Imagine que você precisa criar uma estrutura de dados para armazenar uma lista de elementos. Sem generics, você teria que escrever classes separadas para cada tipo (IntList, StringList, UserList). Com generics, basta uma classe MyList<T> que funcionará com qualquer tipo.



// Sem Generics — é necessário duplicar códigoclass IntList(private val items: MutableList<Int>)class StringList(private val items: MutableList<String>)

// Com Generics — uma classe para todos os tiposclass GenericList<T>(private val items: MutableList<T>) { fun add(item: T) { items.add(item) } fun get(index: Int): T = items[index]}


Neste artigo, vamos detalhar como generics funcionam em Kotlin, abordar sua sintaxe, casos de uso e conceitos avançados como covariância e contravariância.



Fundamentos do trabalho com Generics



Declaração de classes e funções genéricas



Para declarar uma classe genérica, basta especificar um ou mais parâmetros de tipo entre colchetes angulares após o nome da classe:



class Box<T>(var value: T) {    fun getValue(): T = value    fun setValue(newValue: T) { value = newValue }}

// Usoval intBox = Box<Int>(42)val stringBox = Box<String>("Hello")

// Kotlin consegue inferir o tipo, então podemos escrever de forma mais curtaval anotherBox = Box("Auto type inference")


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



fun <T> printItem(item: T) {    println("Item: $item")}

fun <T> createPair(first: T, second: T): Pair<T, T> { return Pair(first, second)}

// UsoprintItem(123)printItem("Text")val pair = createPair("A", "B")


Restrições de tipo (Type Bounds)



Às vezes, é necessário restringir os tipos que podem ser usados em generics. Para isso, são usados limites superiores (upper bounds) com a palavra-chave : Type:



// Apenas tipos que implementam Comparablefun <T : Comparable<T>> findMax(a: T, b: T): T {    return if (a > b) a else b}

// Múltiplas restrições com wherefun <T> processItem(item: T) where T : CharSequence, T : Comparable<T> { println("Length: ${item.length}") println("Compared: ${item.compareTo(item)}")}

// Usoprintln(findMax(10, 20)) // 20println(findMax("Apple", "Banana")) // "Banana"// findMax(10, "Text") // Erro de compilação!


Variância: in, out e projeção de tipos



Um dos tópicos mais complexos, mas importantes em generics, é a variância (variance). Ela define como tipos genéricos se relacionam entre si durante a herança.



Invariância (padrão)



Por padrão, generics em Kotlin são invariantes. Isso significa que Box<Number> e Box<Int> não são subtipos um do outro, mesmo que Int seja um subtipo de Number:



val intBox: Box<Int> = Box(42)// val numBox: Box<Number> = intBox // Erro de compilação!


Covariância (out)



O modificador out torna o tipo covariante. Isso significa que Box<Int> se torna um subtipo de Box<Number>. É usado quando o tipo T é usado apenas em posições de saída (valores de retorno):



class Producer<out T>(private val item: T) {    fun produce(): T = item  // T apenas na saída}                

Blogs

Book Recommendations