Generics memungkinkan satu kelas atau satu fungsi bekerja pada banyak tipe sekaligus tanpa mengorbankan pemeriksaan tipe. Tipe yang dibawa ditulis sebagai parameter tipe (type parameter) dalam tanda kurung sudut, lalu compiler memeriksa setiap pemanggilan terhadap parameter itu. Topik ini membahas parameter tipe pada kelas, constraint tunggal pada fungsi generic, constraint ganda dengan where, serta pesan error yang muncul bila constraint dilanggar. Seluruh contoh dieksekusi dengan compiler Kotlin sehingga keluarannya berasal dari program yang benar-benar berjalan.
Parameter tipe pada kelas#
Kelas generic mendeklarasikan parameter tipe setelah namanya, misalnya Kotak<T>. Di dalam kelas, T dipakai seperti tipe biasa, baik sebagai tipe properti maupun sebagai tipe kembalian fungsi. Saat instans dibuat, compiler mengisi T dari argumen konstruktornya atau dari anotasi tipe pada variabelnya, lalu seluruh akses pada instans tersebut diperiksa terhadap tipe hasil isian itu.
class Kotak<T>(private val isi: T) {
fun ambil(): T = isi
override fun toString(): String = "Kotak berisi $isi"
}
fun <T : Comparable<T>> nilaiTerbesar(daftar: List<T>): T =
daftar.reduce { tercatat, t -> if (t > tercatat) t else tercatat }
fun <T> ringkas(daftar: List<T>): String
where T : Number, T : Comparable<T> {
val terbesar = daftar.reduce { tercatat, t ->
if (t.toDouble() > tercatat.toDouble()) t else tercatat
}
return "jumlah=${daftar.size}, terbesar=${terbesar.toDouble()}"
}
fun main() {
val kotakTeks = Kotak("catatan suhu")
val kotakAngka: Kotak<Int> = Kotak(24)
println(kotakTeks)
println(kotakAngka.ambil() + 1)
println(nilaiTerbesar(listOf(80, 65, 90, 55, 75)))
println(nilaiTerbesar(listOf("Ali", "Budi", "Citra")))
println(ringkas(listOf(80, 65, 90, 55, 75)))
}# compiler Kotlin dan pustaka standar yang dipakai jalur ini
export PATH=/home/kal/my-project/tools/kotlinc/bin:/home/kal/my-project/tools/jdk-merged/bin:$PATH
export STDLIB=/home/kal/my-project/tools/kotlinc/lib/kotlin-stdlib.jar
# kompilasi berkas main.kt menjadi out.jar, lalu jalankan programnya
kotlinc main.kt -d out.jar && java -cp "out.jar:$STDLIB" MainKtKotak berisi catatan suhu
25
90
Citra
jumlah=5, terbesar=90.0Satu deklarasi kelas Kotak melayani dua tipe sekaligus. Pada Kotak("catatan suhu"), compiler menginferensi T sebagai String, sedangkan anotasi Kotak<Int> pada variabel kedua menyatakan isinya secara eksplisit. Pemanggilan kotakAngka.ambil() + 1 sah karena compiler tahu ambil() mengembalikan Int, sehingga penjumlahannya diperiksa dan menghasilkan 25. Tanpa generics, kelas serupa harus ditulis ulang untuk setiap tipe, atau dipaksa memakai Any yang menghilangkan pemeriksaan tipe pada titik pemakaiannya.
Constraint pada parameter tipe#
Constraint membatasi tipe yang boleh mengisi parameter tipe dengan bentuk T : TipeBatasan. Fungsi nilaiTerbesar memakai <T : Comparable<T>>, yang menyatakan bahwa T harus dapat dibandingkan dengan dirinya sendiri. Batasan ini diperlukan karena badan fungsinya memakai operator > pada elemen daftar. Tipe Int dan String sama-sama mengimplementasikan Comparable terhadap dirinya sendiri, sehingga pemanggilan pada kedua daftar sah dan menghasilkan 90 untuk angka serta Citra untuk teks.
Constraint menjadikan syarat pemanggilan tertulis pada deklarasi, bukan tersimpan di kepala penulisnya. Apabila constraint dilanggar, compiler menolak pemanggilan tersebut dengan pesan error, sebagaimana diperlihatkan pada bagian Pelanggaran batasan di bawah. Tanpa constraint, badan fungsi yang membandingkan elemen tidak akan pernah lolos kompilasi, karena compiler tidak dapat menjamin bahwa > tersedia untuk setiap T yang mungkin dipakai.
Constraint juga membuka akses anggota pada parameter tipe. Di dalam fungsi tanpa constraint, nilai bertipe T hanya boleh diperlakukan sebagai Any?, sedangkan dengan batasan Comparable<T>, compiler mengetahui bahwa setiap T punya seluruh anggota interface tersebut. Pemanggilan seperti compareTo pun dapat ditulis langsung tanpa pengecoran tipe, dan pemanggil yang membaca deklarasinya langsung mengetahui syarat tipenya.
Constraint ganda dengan where#
Bila satu batasan tidak cukup, beberapa constraint dinyatakan dengan where yang ditulis setelah daftar parameter fungsi. Fungsi ringkas memakai where T : Number, T : Comparable<T> karena membutuhkan keduanya sekaligus. Kontrak Number dipakai untuk memanggil toDouble(), sedangkan Comparable dipertahankan sebagai batasan tambahan yang menyatakan elemennya dapat dibandingkan. Hasilnya adalah ringkasan jumlah=5, terbesar=90.0 untuk lima nilai ujian pada contoh.
Klausa where juga menjaga baris deklarasi tetap terbaca. Bentuk fun <T> ringkas(...) menuliskan nama dan parameternya secara utuh terlebih dahulu, lalu daftar batasannya mengikuti pada baris berikutnya. Pola ini semakin berguna apabila jumlah batasan bertambah, karena setiap batasan berdiri pada tempat yang jelas dan mudah dibandingkan satu sama lain. Urutan penulisan batasan di dalam klausa tidak berpengaruh, sehingga susunannya bebas mengikuti keterbacaan.
Pelanggaran batasan#
Kelas Titik berikut tidak mengimplementasikan Comparable apa pun, sehingga pemanggilan nilaiTerbesar dengan daftar Titik menuntut T yang tidak memenuhi constraint. Compiler menolaknya pada saat kompilasi sebagaimana berikut.
fun <T : Comparable<T>> nilaiTerbesar(daftar: List<T>): T =
daftar.reduce { tercatat, t -> if (t > tercatat) t else tercatat }
class Titik(val x: Int, val y: Int)
fun main() {
println(nilaiTerbesar(listOf(Titik(1, 2), Titik(3, 4))))
}# kompilasi berkas salahbatas.kt untuk melihat pesan error dari compiler
kotlinc salahbatas.kt -d salahbatas.jarsalahbatas.kt:7:13: error: cannot infer type for type parameter 'T'. Specify it explicitly.
println(nilaiTerbesar(listOf(Titik(1, 2), Titik(3, 4))))
^^^^^^^^^^^^^
salahbatas.kt:7:27: error: argument type mismatch: actual type is 'List<Titik>', but 'List<uninferred T (of fun <T : Comparable<T>> nilaiTerbesar)>' was expected.
println(nilaiTerbesar(listOf(Titik(1, 2), Titik(3, 4))))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^Compiler tidak dapat menginferensi T karena satu-satunya kandidat, yaitu Titik, tidak mengimplementasikan Comparable<Titik> sebagaimana dituntut constraint. Pesan cannot infer type for type parameter 'T' muncul lebih dulu, lalu pesan argument type mismatch menunjuk argumen daftarnya. Kedua pesan itu ditulis pada baris pemanggilannya, bukan pada baris deklarasinya, karena deklarasi fungsi itu sendiri sah dan pemeriksaan constraint dilakukan untuk setiap pemanggilan.
Perbaikannya ada dua arah. Buat Titik mengimplementasikan Comparable<Titik> dengan aturan perbandingan yang jelas, misalnya membandingkan x lebih dahulu, atau ganti constraint apabila fungsi memang tidak memerlukan perbandingan. Dengan perbaikan pertama, fungsi yang sama dapat dipakai tanpa penyuntingan apa pun, karena Titik kini memenuhi syarat yang sudah tertulis pada deklarasinya.
Kode pada berkas ini tidak pernah mencapai tahap eksekusi, sehingga tidak ada keluaran program yang dihasilkan. Pemeriksaan semacam inilah yang membedakan generics dari pendekatan pengecoran manual, yang baru meledak sebagai error saat program berjalan.
Ringkasan#
- Kelas dan fungsi generic mendeklarasikan parameter tipe dalam tanda kurung sudut, dan compiler mengisinya dari pemanggilan maupun anotasi tipe.
- Constraint berbentuk
T : TipeBatasanmembatasi tipe yang sah, misalnya<T : Comparable<T>>untuk tipe yang dapat dibandingkan. - Constraint ganda ditulis dengan
wheresetelah daftar parameter, misalnyawhere T : Number, T : Comparable<T>. - Pemanggilan yang melanggar constraint ditolak compiler sebelum program dijalankan, lengkap dengan pesan error yang menunjuk argumennya.
Referensi#
- Generics: in, out, where — Kotlin Documentation — Parameter tipe, constraint, dan klausa where pada dokumentasi resmi Kotlin.