«out» em Kotlin: entendendo a covariância com exemplos e perguntas

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

O que está por trás da palavra out em Kotlin? Respondendo às perguntas populares

\n\n

Se 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.

\n\n

Vamos entender de uma vez por todas — sem teoria chata, apenas código vivo e prática.

\n\n

Por que Kotlin criou o out? (Uma breve viagem pela história)

\n\n

Em 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.

\n\n

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.

\n\n

Pergunta 1: Qual a diferença entre out e in?

\n\n

A 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
\n\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\n

Observe: Producer<out T> permite atribuir Producer<String> a uma variável do tipo Producer<Any>. Sem out, isso seria impossível.

\n\n

Pergunta 2: Por que Kotlin proíbe fun set(value: T) com out?

\n\n

Esta é uma armadilha clássica. Se você declarou out, o compilador não permitirá funções que recebam T como parâmetro. Por quê?

\n\n

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\n
// 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\n

Solução: se você precisa tanto ler quanto escrever — use in e out separadamente, ou um tipo invariante (sem modificador).

\n\n

Pergunta 3: Como out se relaciona com o wildcard ? extends do Java?

\n\n

Correspondência direta: Source<out T> em Kotlin = Source<? extends T> em Java. Mas há uma diferença importante:

\n\n
    \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
\n\n

Exemplo em Java:

\n\n
// Java: usando wildcard na declaração da variável\nList<? extends Number> numbers = new ArrayList<Integer>();\nnumbers.a                

Blogs

Book Recommendations