Menu
CoddyTech

Varians dengan in dan out

Termasuk bagian Pemrograman Berorientasi Objek dalam perjalanan Kotlin di Coddy. Pelajaran 40 dari 57.

List<String> dapat diberikan ketika yang diharapkan adalah List<Any>, karena List hanya mengeluarkan elemen. MutableList<String> tidak bisa: kode yang melihatnya sebagai MutableList<Any> dapat menambahkan Int ke list string:

fun describeAll(items: List<Any>) = items.joinToString { it::class.simpleName ?: "?" }
fun addNumber(items: MutableList<Any>) { items.add(42) }

Di dalam main:

val words = listOf("a", "b")
println(describeAll(words))
val mixed = mutableListOf<Any>("x")
addNumber(mixed)
println(mixed)
// addNumber(mutableListOf("a"))   // error: MutableList<String> bukan MutableList<Any>

Output:

String, String
[x, 42]

out T membuat generic class kovarian: class ini hanya menghasilkan nilai T sebagai tipe kembalian dan tidak pernah menerimanya. Jadi, Producer<Cat> dianggap sebagai 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}"

Di dalam main:

val cats: Producer<Cat> = Producer(Cat("Tom"))
println(greet(cats))

Output:

hello Tom

in T menjadikannya kontravarian: hanya menerima nilai T sebagai parameter. Konsumen yang menangani semua Animal dapat digunakan sebagai pengganti konsumen 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) }

Di dalam main:

println(feedCats(listOf(Cat("Tom"), Cat("Kit")), AnimalFeeder()))

Output:

[fed Tom, fed Kit]

Kompilator menegakkan arah tersebut: parameter tipe out tidak dapat menjadi tipe parameter method, dan parameter tipe in tidak dapat dikembalikan. Aturan praktisnya adalah produsen out, konsumen in. Pustaka standar menerapkan hal yang sama: List<out E>, Comparator<in T>. Tanpa salah satu kata kunci tersebut, parameter tipe bersifat invarian, seperti pada MutableList<E>, yang dapat membaca dan menulis:

class Producer<out T>(private val item: T) {
    fun get(): T = item                  // benar: T keluar
    // fun set(x: T) { }                 // error: T dideklarasikan sebagai out
}
interface Feeder<in T> {
    fun feed(animal: T): String          // benar: T masuk
    // fun last(): T                     // error: T dideklarasikan sebagai in
}

Variance juga dapat ditulis di tempat tipe digunakan. MutableList<in Int> menerima mutable list apa pun yang dapat menampung nilai Int, seperti MutableList<Number>, sedangkan MutableList<out Number> hanya memungkinkan pembacaan:

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() }

Di dalam main:

val numbers = mutableListOf<Number>(0.5)
fillWith(numbers, 3)
println(numbers)
println(total(mutableListOf(1, 2, 3)))

Output:

[0.5, 1, 2, 3]
6.0
challenge icon

Tantangan

Mudah

Dua interface memindahkan item: Source<out T> menghasilkan item dengan next() (mengembalikan null saat kosong), sedangkan Sink<in T> mengonsumsinya dengan accept(item). Keduanya sudah disediakan. Tulis ListSource<T>(items), yang mengembalikan item-item dari list satu per satu lalu null; Collector<T>, yang menyimpan item yang diterima dalam list items hanya-baca; dan Counter, yaitu Sink<Any> yang menghitung semua item yang diterimanya. Lalu tulis pipe(from, vararg to), yang memindahkan setiap item dari source ke setiap sink dan mengembalikan jumlah item yang dipindahkannya.

Kode yang disediakan menyalurkan satu baris kata ke Collector<String> dan satu baris angka ke Collector<Number>, keduanya juga disalurkan ke satu Counter. Ini berfungsi karena out dan in: ListSource<Int> adalah Source<Number>, dan Sink<Any> adalah Sink<String>. Kode ini mencetak jumlah hitungan, kedua koleksi, counted: 7 dan after the end: null.

Kodemu ditulis dalam ListSource.kt, Collector.kt, Counter.kt, Pipe.kt, Source.kt dan Sink.kt. Main.kt berisi kode input/output yang disediakan dan tidak boleh diedit.

Coba sendiri

fun main() {
    // Kode input/output yang disediakan: biarkan apa adanya
    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()}")
}
quiz iconUji dirimu

Pelajaran ini berisi kuis singkat. Mulai pelajarannya untuk menjawab kuis dan melacak progresmu.

Semua pelajaran di Pemrograman Berorientasi Objek

Latihan sendiri: Playground Kotlin