Variância com in e out
Parte da seção Programação Orientada a Objetos do Journey de Kotlin da Coddy. Lição 40 de 57.
Uma List<String> pode ser passada onde uma List<Any> é esperada, porque uma List apenas fornece elementos. Uma MutableList<String> não pode: o código que a enxergar como MutableList<Any> poderia adicionar um Int a uma lista de strings:
fun describeAll(items: List<Any>) = items.joinToString { it::class.simpleName ?: "?" }
fun addNumber(items: MutableList<Any>) { items.add(42) }Dentro de main:
val words = listOf("a", "b")
println(describeAll(words))
val mixed = mutableListOf<Any>("x")
addNumber(mixed)
println(mixed)
// addNumber(mutableListOf("a")) // erro: MutableList<String> não é um MutableList<Any>Saída:
String, String
[x, 42]out T torna uma classe genérica covariante: ela apenas produz valores T, como tipos de retorno, e nunca os recebe. Portanto, um Producer<Cat> é considerado um Producer<Animal>:
open class Animal(val name: String)
class Cat(name: String) : Animal(name)
class Producer<out T>(private val item: T) {
fun get(): T = item
}
fun greet(p: Producer<Animal>) = "hello ${p.get().name}"Dentro de main:
val cats: Producer<Cat> = Producer(Cat("Tom"))
println(greet(cats))Saída:
hello Tomin T torna-o contravariante: ele apenas consome valores de T, como parâmetros. Um consumidor que lida com qualquer Animal pode substituir um consumidor de Cat:
open class Animal(val name: String)
class Cat(name: String) : Animal(name)
interface Feeder<in T> {
fun feed(animal: T): String
}
class AnimalFeeder : Feeder<Animal> {
override fun feed(animal: Animal) = "fed ${animal.name}"
}
fun feedCats(cats: List<Cat>, feeder: Feeder<Cat>) = cats.map { feeder.feed(it) }Dentro de main:
println(feedCats(listOf(Cat("Tom"), Cat("Kit")), AnimalFeeder()))Saída:
[fed Tom, fed Kit]O compilador impõe a direção: um parâmetro de tipo out não pode ser o tipo de um parâmetro de método, e um parâmetro in não pode ser retornado. A regra prática é: produtor out, consumidor in. A biblioteca padrão faz o mesmo: List<out E>, Comparator<in T>. Sem nenhuma dessas palavras-chave, um parâmetro de tipo é invariante, como em MutableList<E>, que tanto lê quanto escreve:
class Producer<out T>(private val item: T) {
fun get(): T = item // ok: T sai
// fun set(x: T) { } // erro: T é declarado out
}
interface Feeder<in T> {
fun feed(animal: T): String // ok: T entra
// fun last(): T // erro: T é declarado in
}A variância também pode ser escrita onde um tipo é usado. MutableList<in Int> aceita qualquer lista mutável que possa receber valores Int, como uma MutableList<Number>, e MutableList<out Number> permite apenas a leitura:
fun fillWith(target: MutableList<in Int>, n: Int) {
for (i in 1..n) target.add(i)
}
fun total(source: MutableList<out Number>) = source.sumOf { it.toDouble() }Dentro de main:
val numbers = mutableListOf<Number>(0.5)
fillWith(numbers, 3)
println(numbers)
println(total(mutableListOf(1, 2, 3)))Saída:
[0.5, 1, 2, 3]
6.0Desafio
FácilDuas interfaces movem itens: Source<out T> produz itens com next() (retornando null quando está vazia) e Sink<in T> os consome com accept(item). Elas são fornecidas. Escreva ListSource<T>(items), que retorna os itens da lista um por um e depois null; Collector<T>, que mantém os itens aceitos em uma lista items somente para leitura; e Counter, um Sink<Any> que conta tudo o que aceita. Em seguida, escreva pipe(from, vararg to), que move cada item da fonte para cada destino e retorna quantos itens moveu.
O código fornecido direciona uma linha de palavras para um Collector<String> e uma linha de números para um Collector<Number>, nas duas vezes também para um único Counter. Isso funciona por causa de out e in: um ListSource<Int> é um Source<Number>, e um Sink<Any> é um Sink<String>. Ele imprime as contagens, ambas as coleções, counted: 7 e after the end: null.
Seu código deve ficar em ListSource.kt, Collector.kt, Counter.kt, Pipe.kt, Source.kt e Sink.kt. Main.kt contém o código fornecido de entrada/saída e não pode ser editado.
Experimente você mesmo
fun main() {
// Código de entrada/saída fornecido: mantenha-o como está
val input = generateSequence(::readLine).toList()
val words = ListSource(input[0].split(" "))
val ints = ListSource(input[1].split(" ").map { it.toInt() })
val counter = Counter()
val allWords = Collector<String>()
val numbers = Collector<Number>()
println("words moved: " + pipe(words, allWords, counter))
println("numbers moved: " + pipe(ints, numbers, counter))
println(allWords.items)
println(numbers.items)
println("counted: ${counter.count}")
println("after the end: ${words.next()}")
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Classes e Objetos
Trabalhando com ArquivosDefinindo ClassesConstrutores PrimáriosBlocos init e ValidaçãoConstrutores SecundáriosRecapitulação - Cartão da Biblioteca4Herança
Classes Abertas e SubclassesSobrescrevendo Membrossuper e ConstrutoresClasses AbstratasRevisão - Diretório de Funcionários2Propriedades e Encapsulamento
Acessores PersonalizadosModificadores de VisibilidadeSetters Privadoslazy e lateinitRevisão - Conta Bancária5Interfaces
Declarando InterfacesMétodos PadrãoMúltiplas InterfacesInterfaces como TiposRecapitulação - Métodos de Pagamento8Genéricos
Funções genéricasClasses genéricasRestrições de tipoVariância com in e outRecapitulação - Fila de prioridade11Projeto: Gerenciamento de Biblioteca
Livros e MembrosEmpréstimo de LivrosPratique por conta própria: Playground de Kotlin