* e Any em Kotlin: qual a diferença e o que escolher? Guia completo

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 a confusão entre * e Any é normal

\n\n

Se você programa em Kotlin há pelo menos algumas semanas, com certeza já esbarrou em generics. E se você já deu uma olhada no código-fonte da biblioteca padrão ou escreveu algo como List<*>, provavelmente se perguntou: "Por que não escrever simplesmente List<Any>? Afinal, o asterisco parece significar 'qualquer tipo', não?"

\n\n

Essa pergunta é um verdadeiro sucesso no Stack Overflow (milhares de visualizações). E não é para menos: à primeira vista, * e Any parecem intercambiáveis. Mas, na prática, existe um abismo entre eles — e se você não perceber isso, pode acabar escrevendo um código que não compila ou, pior ainda, que quebra silenciosamente em tempo de execução.

\n\n

Neste artigo, vamos entender de uma vez por todas as diferenças entre * (star projection) e Any nos generics do Kotlin. Você vai aprender quando usar cada um, como a variância funciona e por que o asterisco não é um "Any", mas sim um "não sei o que tem ali, mas não me importo". Vamos nessa!

\n\n

Um resumo sobre generics: por que eles existem

\n\n

Generics (ou tipos genéricos) nos permitem escrever classes e funções que funcionam com diferentes tipos sem perder a segurança de tipos. Em vez de criar uma classe separada para Int, outra para String e outra para Usuario, criamos uma única classe Caixa<T>.

\n\n

Mas o que fazer quando queremos escrever uma função que aceite qualquer Caixa, independentemente do tipo interno? É aí que entram nossos protagonistas: * e Any.

\n\n

Any é um tipo concreto (quase como Object)

\n\n

Em Kotlin, Any é a raiz da hierarquia de tipos para todos os tipos não-nulos. Se você escreve List<Any>, isso significa: "uma lista que contém elementos do tipo Any". Ou seja, você pode colocar Int, String, Usuario — qualquer coisa, porque todos eles herdam de Any.

\n\n

Ponto importante: List<Any> é um tipo parametrizado concreto. O compilador sabe que o tipo do elemento é Any.

\n\n
val listaDeAny: MutableList<Any> = mutableListOf(1, "olá", 3.14)\nlistaDeAny.add(42) // OK\nlistaDeAny.add("mundo") // OK\n
\n\n

Aqui tudo é previsível: dissemos explicitamente que a lista trabalha com Any. Mas há um detalhe: MutableList<Any> não é MutableList<String>. Você não pode passar uma lista de strings para onde se espera uma lista de Any (a menos que use covariância, mas falaremos disso depois).

\n\n

O asterisco (*) é um "não sei, mas não me importo"

\n\n

O asterisco, ou star projection, é uma história completamente diferente. Quando você escreve List<*>, está dizendo ao compilador: "Esta é uma lista de algum tipo, mas não sei qual é, e isso não é importante para mim". Isso é uma projeção de tipo desconhecido.

\n\n

A diferença fundamental: * não é um tipo, mas um wildcard (curinga). Ele indica que o tipo é desconhecido, e o compilador impõe restrições sobre o que você pode fazer com esse objeto.

\n\n
fun imprimirTamanho(lista: List<*>) {\n    // lista.add(1) // Erro de compilação! Não é possível adicionar elemento\n    println(lista.size) // OK: podemos saber o tamanho\n    for (item in lista) {\n        println(item) // OK: item tem tipo Any?\n    }\n}\n
\n\n

Observe: ao ler elementos de List<*>, eles têm o tipo Any? (porque a lista pode conter null). Mas você não pode adicionar elementos, pois o compilador não sabe qual tipo está realmente armazenado ali. Isso é uma proteção contra a violação da segurança de tipos.

\n\n

A principal diferença em poucas palavras

\n\n
    \n
  • List<Any> — é uma lista que pode conter

Blogs

Book Recommendations