O que está por trás da palavra out em Kotlin? Respondendo às perguntas populares
\n\nSe você já escreveu genéricos em Kotlin e se deparou com um erro de compilação confuso, provavelmente encontrou a palavra out. Este pequeno modificador gera muita discussão até mesmo entre desenvolvedores experientes. Reunimos as perguntas mais frequentes do Stack Overflow e as transformamos em um guia claro. Hoje você aprenderá o que é out, para que serve e como não se confundir com a covariância.
Vamos entender de uma vez por todas — sem teoria chata, apenas código vivo e prática.
\n\nPor que Kotlin criou o out? (Uma breve viagem pela história)
\n\nEm Java, existe o wildcard — o ponto de interrogação ? extends T. Ele significa que a coleção pode conter objetos do tipo T ou seus subtipos. O problema é que o Java não impede que você coloque acidentalmente um objeto errado em tal coleção — você só receberá um erro em tempo de execução. Kotlin resolveu esse problema no nível dos tipos.
A palavra-chave out em Kotlin é uma declaração de covariância. Se você escreve Source<out T>, significa: "Posso ler objetos do tipo T deste Source, mas não posso escrever nele". O compilador verifica isso rigorosamente.
Pergunta 1: Qual a diferença entre out e in?
\n\nA pergunta mais frequente no Stack Overflow. Vamos imaginar uma caixa:
\n\n- \n
out— uma caixa da qual você só pode retirar coisas (produz). \n in— uma caixa na qual você só pode colocar coisas (consome). \n
Exemplo:
\n\n// Covariância: podemos ler, mas não escrever\ninterface Producer<out T> {\n fun produce(): T\n}\n\n// Contravariância: podemos escrever, mas não ler\ninterface Consumer<in T> {\n fun consume(item: T)\n}\n\nfun main() {\n val stringProducer: Producer<String> = object : Producer<String> {\n override fun produce(): String = "Hello"\n }\n \n val anyProducer: Producer<Any> = stringProducer // Funciona! out permite\n println(anyProducer.produce())\n}\n\n\nObserve: Producer<out T> permite atribuir Producer<String> a uma variável do tipo Producer<Any>. Sem out, isso seria impossível.
Pergunta 2: Por que Kotlin proíbe fun set(value: T) com out?
\n\nEsta é uma armadilha clássica. Se você declarou out, o compilador não permitirá funções que recebam T como parâmetro. Por quê?
Imagine que você tem um MutableList<out Number>. Você pode colocar um Int nele, mas o compilador não sabe que tipo de lista é realmente. Talvez seja um MutableList<Double>? Se a escrita fosse permitida, você quebraria os tipos. out garante segurança: você só pode ler.
// Não compila!\ninterface BadList<out T> {\n fun get(index: Int): T // OK\n fun add(item: T) // Erro! T está em posição in\n}\n\n\nSolução: se você precisa tanto ler quanto escrever — use in e out separadamente, ou um tipo invariante (sem modificador).
\n\nPergunta 3: Como out se relaciona com o wildcard ? extends do Java?
\n\nCorrespondência direta: Source<out T> em Kotlin = Source<? extends T> em Java. Mas há uma diferença importante:
- \n
- Em Java, o wildcard é uma variância no uso (use-site variance). \n
- Em Kotlin,
outé uma variância na declaração (declaration-site variance). \n
Exemplo em Java:
\n\n// Java: usando wildcard na declaração da variável\nList<? extends Number> numbers = new ArrayList<Integer>();\nnumbers.a