Saat kita menulis class Box<T>, type parameter T tidak memiliki batasan — semua tipe boleh dimasukkan. Pada banyak kasus, kita ingin membatasi tipe yang boleh dipakai sebagai type argument. Misalnya, sebuah class yang menghitung operasi matematika hanya boleh menerima Number dan turunannya. Batasan ini disebut upper bound dan ditulis dengan tanda titik dua. Pada chapter ini kita akan mempelajari bound tunggal, multiple bounds dengan klausa where, serta melihat bagaimana kombinasi bound memungkinkan kita menulis kode generic yang tetap kaya fitur.
Bound Tunggal #
Bound tunggal membatasi type parameter ke satu tipe saja. Sintaksisnya adalah <T : BoundType>. Setelah bound dipasang, semua method yang dimiliki BoundType dapat dipanggil di dalam body class atau function generic.
class NumberBox<T : Number>(val value: T) {
fun doubled(): Double = value.toDouble() * 2
}Karena T dibatasi menjadi Number, method toDouble tersedia. Tanpa bound, kompilator menolak pemanggilan tersebut karena T dianggap sebagai Any?.
Multiple Bounds dengan where #
Kadang satu bound tidak cukup. Sebagai contoh, kita ingin type parameter T memenuhi dua kondisi sekaligus — Number dan Comparable<T>. Untuk kasus ini, Kotlin menyediakan klausa where yang ditaruh setelah deklarasi parameter constructor atau sebelum body function.
class ItemRingkas<T>(val data: T) where T : Number, T : DapatDijelaskan {
fun ringkas(): String = "Nilai ${data} (${data.jelaskan()})"
}Klausa where T : Number, T : DapatDijelaskan memaksa T harus subtype dari Number sekaligus mengimplementasikan DapatDijelaskan. Hal ini memungkinkan kita memanggil data.toDouble() (dari Number) dan data.jelaskan() (dari DapatDijelaskan) di dalam body class.
Contoh Lengkap dengan Class Kustom #
Untuk mendemonstrasikan multiple bound, kita perlu class yang memenuhi semua kondisi. Pada contoh berikut, kita membuat class MyNumber yang meng-extend Number sekaligus mengimplementasikan Comparable<MyNumber> dan DapatDijelaskan:
// Chapter: Upper Bounds
// Membatasi type parameter dengan upper bound tunggal dan jamak.
class NumberBox<T : Number>(val value: T) {
fun doubled(): Double = value.toDouble() * 2
fun isGreaterThan(other: Number): Boolean = value.toDouble() > other.toDouble()
}
interface DapatDijelaskan {
fun jelaskan(): String
}
class ItemRingkas<T>(val data: T) where T : Number, T : DapatDijelaskan {
fun ringkas(): String = "Nilai ${data} (${data.jelaskan()})"
}
class MetricNumber<T>(val nilai: T) : DapatDijelaskan where T : Number, T : Comparable<T> {
override fun jelaskan(): String = "angka $nilai"
fun bandingkanDengan(lain: T): Int = nilai.compareTo(lain)
}
// Bound dengan tipe parameter lain
class KomparatorBox<T : Comparable<T>>(val a: T, val b: T) {
fun yangTerbesar(): T = if (a >= b) a else b
}
// Fungsi generik dengan multiple where
fun <T> jumlahkan(a: T, b: T): Double where T : Number, T : Comparable<T> {
return if (a >= b) a.toDouble() + b.toDouble() else b.toDouble() + a.toDouble()
}
@Suppress("DEPRECATION", "OverridingDeprecatedMember")
class MyNumber(val value: Int) : Number(), Comparable<MyNumber>, DapatDijelaskan {
override fun toDouble(): Double = value.toDouble()
override fun toFloat(): Float = value.toFloat()
override fun toLong(): Long = value.toLong()
override fun toInt(): Int = value
override fun toChar(): Char = value.toChar()
override fun toByte(): Byte = value.toByte()
override fun toShort(): Short = value.toShort()
override fun compareTo(other: MyNumber): Int = value.compareTo(other.value)
override fun jelaskan(): String = "MyNumber($value)"
override fun toString(): String = "MyNumber($value)"
}
fun main() {
val kotak1 = NumberBox(10)
println("Doubled: ${kotak1.doubled()}")
println("Lebih besar dari 5? ${kotak1.isGreaterThan(5)}")
val kotak2 = NumberBox(3.14)
println("Doubled (double): ${kotak2.doubled()}")
val cb = KomparatorBox("Mangga", "Apel")
println("Terbesar: ${cb.yangTerbesar()}")
val cbInt = KomparatorBox(42, 17)
println("Terbesar (int): ${cbInt.yangTerbesar()}")
val jumlah = jumlahkan(MyNumber(5), MyNumber(8))
println("Jumlahkan: $jumlah")
val m = MetricNumber(MyNumber(7))
val banding = m.bandingkanDengan(MyNumber(3))
println("Bandingkan MyNumber(7) vs MyNumber(3): $banding")
val item = ItemRingkas(MyNumber(15))
println(item.ringkas())
}Output yang Dihasilkan:
Doubled: 20.0
Lebih besar dari 5? true
Doubled (double): 6.28
Terbesar: Mangga
Terbesar (int): 42
Jumlahkan: 13.0
Bandingkan MyNumber(7) vs MyNumber(3): 1
Nilai MyNumber(15) (MyNumber(15))Mari kita telaah baris-baris kode di atas:
class NumberBox<T : Number>: bound tunggal pada level deklarasi class.Tharus subtypeNumber.class ItemRingkas<T>(val data: T) where T : Number, T : DapatDijelaskan: multiple bounds denganwhere. Koma memisahkan setiap bound.Tharus memenuhi keduanya.class MetricNumber<T>(val nilai: T) : DapatDijelaskan where T : Number, T : Comparable<T>: class generic yang juga mengimplementasikan interface. Boundwhereditaruh setelah deklarasi constructor dan sebelum body class.class KomparatorBox<T : Comparable<T>>: bound tunggal yang merupakan recursive bound —Tdibandingkan dengan dirinya sendiri.fun <T> jumlahkan(a: T, b: T): Double where T : Number, T : Comparable<T>: function generic dengan multiple bounds. Di dalam body, kita dapat memanggila.toDouble()dan membandingkana >= bkarena kedua method tersedia dari bound masing-masing.@Suppress("DEPRECATION", "OverridingDeprecatedMember"): anotasi untuk menenangkan peringatan kompilator terkait methodtoCharyang ditandai deprecated pada classNumber.class MyNumber : Number(), Comparable<MyNumber>, DapatDijelaskan: class kustom yang memenuhi semua bound. Wajib meng-override method abstrakNumber(toInt,toDouble, dan seterusnya).ItemRingkas(MyNumber(15)): karenaMyNumbermemenuhiNumberdanDapatDijelaskan, kompilator menerima pemanggilan. Methodringkasmemanggildata.toDouble()dandata.jelaskan()— keduanya tersedia karena multiple bound.
Kapan Memakai Multiple Bounds #
Multiple bounds cocok ketika kita menulis function generic yang membutuhkan beberapa perilaku sekaligus. Misalnya, function yang membandingkan dan menjumlahkan harus memastikan type argument adalah Comparable sekaligus Number. Namun, semakin banyak bound, semakin sempit set tipe yang memenuhi syarat — pertimbangkan apakah class biasa dengan antarmuka yang sama lebih sederhana.
Referensi Lanjutan #
- Generics: Variance
— bagian upper bounds (
T : Comparable<T>) pada panduan generics
Bound memengaruhi fleksibilitas generic, namun ada topik terpisah yang berkaitan langsung dengan subtype relationship: variance. Pada chapter Variance kita akan melihat mengapa List<String> bukan subtype List<Any> meskipun String adalah subtype Any.