Sebelumnya kita telah mempelajari bagaimana generic bekerja, bagaimana penerapannya, serta bagaimana kita bisa menentukan batasan tipe argumen yang bisa ditentukan terhadap tipe parameter. Selanjutnya kita akan belajar salah satu konsep dari generic yaitu variance.

Apa itu variance? Variance adalah konsep yang menggambarkan bagaimana sebuah tipe yang memiliki subtipe yang sama dan tipe argumen yang berbeda saling berkaitan satu sama lain. Variance dibutuhkan ketika kita ingin membuat kelas atau fungsi generic dengan batasan yang tidak akan mengganggu dalam penggunaannya. Sebagai contoh, mari kita buat beberapa kelas seperti berikut:

1. abstract class Vehicle(wheel: Int) 2. class Car(speed: Int) : Vehicle(4) 3. class MotorCycle(speed: Int) : Vehicle(2)

Kemudian jalankan kode seperti berikut:

1. fun main() { 2.     val car = Car(200) 3.     val motorCycle = MotorCycle(100) 4.     var vehicle: Vehicle = car 5.     vehicle = motorCycle 6. }

Bisa kita perhatikan pada kode di atas, variabel car dan motorcycle merupakan subtipe dari Vehicle sehingga kita bisa melakukan assignment antar dua variabel tersebut. Maka seharusnya kode tersebut akan berhasil dikompilasi.

Selanjutnya mari kita masukkan salah satu kelas yang merupakan subtipe dari kelas Vehicle di atas kedalam generic list:

1. fun main() { 2.     val carList = listOf(Car(100) , Car(120)) 3.     val vehicleList = carList 4. }

Dari contoh di atas, kita melihat bagaimana variance menggambarkan keterkaitan antara carList dan vehicleList di mana Car merupakan subtipe dari Vehicle.

Nah, itu adalah contoh sederhana bagaimana variance bekerja. Lalu bagaimana cara membuat kelas generic yang memiliki variance? Caranya sama seperti ketika kita membuat generic kelas pada umumnya. Namun untuk tipe parameternya kita membutuhkan kata kunci out untuk covariant atau kunci in untuk contravariant.

Covariant

Contoh deklarasi generic dengan covariant bisa kita lihat saat kelas List pada Kotlin dideklarasikan seperti berikut:

1. interface List<out E> : Collection<E> { 2.     operator fun get(index: Int): E 3. }

Ketika kita menandai sebuah tipe parameter dengan kata kunci out maka nilai dari tipe parameter tersebut hanya bisa diproduksi seperti menjadikanya sebagai return type. Serta tidak dapat dikonsumsi seperti menjadikannya sebagai tipe argumen untuk setiap fungsi di dalam kelas tersebut.

Contravariant

Berbanding terbalik dengan saat kita menandainya dengan kata kunci out, bagaimana saat kita menandainya dengan dengan kata kunci in ?  Nilai dari tipe parameter tersebut bisa dikonsumsi dengan menjadikannya sebagai tipe argumen untuk setiap fungsi yang ada di dalam kelas tersebut dan tidak untuk diproduksi. Contoh dari deklarasinya bisa kita lihat pada kelas Comparable pada Kotlin berikut:

1. interface Comparable<in T> { 2.     operator fun compareTo(other: T): Int 3. }