Pada chapter sebelumnya kita melihat bagaimana class generic memungkinkan satu definisi class dipakai untuk banyak tipe. Ide yang sama dapat diterapkan pada level function. Function generic sangat berguna ketika algoritma yang kita tulis bersifat netral terhadap tipe data — misalnya mengambil elemen pertama dari list, memetakan setiap elemen, atau membandingkan dua nilai. Pada chapter ini kita akan mendeklarasikan function generic, mempelajari posisi type parameter yang benar, serta memahami kapan kompilator dapat menyimpulkan tipe secara otomatis dan kapan kita perlu menuliskannya secara eksplisit.
Posisi Type Parameter #
Pada function generic, type parameter ditulis sebelum nama function dengan tanda kurung siku <>. Bentuk umumnya adalah fun <T> namaFunction(arg: T): T. Posisi ini wajib karena type parameter tidak boleh ditulis di tempat lain — misalnya setelah parameter biasa. Kompilator membaca type parameter terlebih dahulu sebelum memproses signature function.
fun <T> firstOrNull(list: List<T>): T? {
return if (list.isEmpty()) null else list[0]
}Pada contoh di atas, T adalah type parameter yang muncul di tiga tempat: parameter list: List<T>, tipe kembalian T?, dan isi function. Saat function dipanggil, kompilator mengikat T ke tipe konkret yang diberikan pemanggil.
Function Generic Sederhana #
Mari kita lihat beberapa function generic sekaligus, mulai dari yang paling sederhana hingga yang memakai bound pada type parameter:
// Chapter: Generic Functions
// Function generic dengan type parameter sebelum nama function.
fun <T> firstOrNull(list: List<T>): T? {
return if (list.isEmpty()) null else list[0]
}
fun <T> cetakSemua(item: List<T>) {
for (e in item) print("$e ")
println()
}
fun <T, R> map(list: List<T>, transform: (T) -> R): List<R> {
val hasil = mutableListOf<R>()
for (e in list) hasil.add(transform(e))
return hasil
}
fun <T : Comparable<T>> maxOf(a: T, b: T): T {
return if (a >= b) a else b
}
fun main() {
val angka = listOf(10, 20, 30)
val kata = listOf("Kotlin", "Java", "Python")
val kosong = listOf<String>()
// Pemanggilan dengan type argument eksplisit
val a1 = firstOrNull<Int>(angka)
println("Pertama angka: $a1")
// Type inference otomatis dari argumen
val a2 = firstOrNull(kata)
println("Pertama kata: $a2")
val a3 = firstOrNull(kosong)
println("Pertama dari list kosong: $a3")
cetakSemua(angka)
cetakSemua(kata)
val kuadrat = map(angka) { it * it }
println("Kuadrat: $kuadrat")
val panjang = map(kata) { it.length }
println("Panjang tiap kata: $panjang")
val m1 = maxOf(7, 12)
println("Max(7, 12) = $m1")
val m2 = maxOf("Mangga", "Apel")
println("Max(\"Mangga\", \"Apel\") = $m2")
}Output yang Dihasilkan:
Pertama angka: 10
Pertama kata: Kotlin
Pertama dari list kosong: null
10 20 30
Kotlin Java Python
Kuadrat: [100, 400, 900]
Panjang tiap kata: [6, 4, 6]
Max(7, 12) = 12
Max("Mangga", "Apel") = ManggaMari kita telaah baris-baris kode di atas:
fun <T> firstOrNull(list: List<T>): T?: type parameterTmuncul sebelum nama function. Tipe kembalianT?menandakan function bisa mengembalikannullketika list kosong.firstOrNull<Int>(angka): pemanggil menentukan type argumentIntsecara eksplisit. Pendekatan ini berguna ketika tipe tidak dapat disimpulkan dari argumen, misalnya list kosong.firstOrNull(kata): kompilator menyimpulkanT = Stringdari argumenkata: List<String>. Kita tidak perlu menuliskanfirstOrNull<String>(kata).fun <T, R> map(list, transform): List<R>: dua type parameter —Tuntuk tipe input danRuntuk tipe output. Lambdatransform: (T) -> Rmengubah elemen bertipeTmenjadiR.map(angka) { it * it }: kompilator menyimpulkanT = Intdariangka: List<Int>. Karenait * itmenghasilkanInt, makaR = Intjuga. Type inference bekerja secara bertahap.fun <T : Comparable<T>> maxOf(a, b): type parameterTdibatasi dengan upper boundComparable<T>. Hal ini memungkinkan kita memakai operator>=di dalam function karenaTdijamin memiliki methodcompareTo.maxOf("Mangga", "Apel"):StringmengimplementasikanComparable<String>, sehingga memenuhi bound. Kompilator menerima pemanggilan ini.
Inference vs Eksplisit #
Sebagian besar kasus, type inference cukup dan kode menjadi ringkas. Saat kita memanggil function generic dengan argumen yang jelas tipenya, kompilator menentukan type parameter secara otomatis. Eksplisit menulis type argument (<Int>) hanya perlu ketika inference tidak cukup — biasanya saat argumen berupa koleksi kosong atau saat kita ingin memaksa tipe yang lebih sempit.
Function Generic dengan Bound #
Bound pada type parameter membatasi tipe yang boleh dipakai. Pada contoh maxOf, bound Comparable<T> memungkinkan kita memakai operator pembanding. Bound juga memengaruhi apa yang dapat dilakukan di dalam function — tanpa bound, kita hanya bisa memperlakukan T sebagai Any? dan tidak bisa memanggil method khusus tipe tertentu. Konsep bound akan kita bedah tuntas pada chapter Upper Bounds.
Referensi Lanjutan #
- Generics — bagian generic function pada panduan generics resmi
- Reified type parameters
— tipe parameter
reifiedyang hanya tersedia pada inline function
Function generic menjadi alat utama ketika kita menulis library utilitas yang berlaku untuk banyak tipe. Standard library Kotlin banyak memakai pola ini, mulai dari listOf<T>, mapOf<K, V>, hingga filter, map, dan fold. Pada chapter Generic Interfaces kita akan melihat bagaimana ide generic diterapkan pada level interface agar kontrak dapat dipakai oleh banyak implementasi.