Type parameter adalah inti dari generics. Type parameter berfungsi sebagai placeholder tipe yang akan diisi kemudian, baik saat instantiasi class generic maupun saat memanggil function generic. Pemilihan nama type parameter memengaruhi keterbacaan kode secara signifikan — kompilator menerima nama apa pun, namun komunitas Kotlin memakai konvensi huruf kapital tunggal yang memiliki makna tertentu. Pada chapter ini kita akan memetakan konvensi penamaan type parameter, memahami kapan menggunakan bound tunggal maupun recursive bound, serta melihat bagaimana bound memengaruhi method yang dapat dipanggil di dalam class atau function generic.

Konvensi Penamaan #

Komunitas Kotlin mewarisi konvensi dari Java: nama type parameter memakai huruf kapital tunggal dengan makna khusus. Berikut pemetaan konvensi yang umum dipakai:

NamaMakna UmumContoh Penggunaan
TType (umum)class Box<T>
EElement (koleksi)class Antrian<E>
KKey (map)class Peta<K, V>
VValue (map)class Peta<K, V>
RReturn (function)fun <R> map(f: () -> R)
T1, T2Beberapa tipe umumfun <T1, T2> gabung(a: T1, b: T2)

Konvensi bukan aturan wajib. Kompilator menerima nama yang lebih deskriptif seperti class Repository<EntityType>. Namun, nama deskriptif jarang dipakai karena biasanya type parameter muncul di banyak tempat dan nama panjang membuat signature menjadi padat.

Bound Tunggal #

Tanpa bound, type parameter hanya bisa diperlakukan sebagai Any?. Artinya, kita tidak bisa memanggil method khusus tipe tertentu. Bound membatasi type parameter ke tipe tertentu, sehingga method pada tipe tersebut dapat dipakai di dalam body. Bound ditulis dengan tanda titik dua setelah nama type parameter.

kotlin Bound tunggal: T : Number
class PenampungAngka<T : Number>(val nilai: T) {
    fun doubleNilai(): Double = nilai.toDouble() * 2
}

Pada contoh di atas, T : Number berarti T harus subtype dari Number. Dengan batasan ini, kita dapat memanggil nilai.toDouble() karena Number memiliki method toDouble. Tanpa bound, kompilator tidak akan mengizinkan pemanggilan method tersebut.

Recursive Bound #

Recursive bound adalah bound yang merujuk pada type parameter itu sendiri. Pola paling umum adalah <T : Comparable<T>>, yang berarti T harus dapat dibandingkan dengan dirinya sendiri. Hal ini memungkinkan kita menulis function umum yang bekerja pada tipe apa pun selama tipe tersebut dapat dibandingkan.

kotlin
// Chapter: Type Parameters
// Konvensi penamaan dan batasan type parameter.

// T: Type umum
class Kotak<T>(val isi: T) {
    fun ambil(): T = isi
}

// E: Element (untuk koleksi)
class Antrian<E> {
    private val item = mutableListOf<E>()
    fun masuk(e: E) = item.add(e)
    fun keluar(): E? = if (item.isEmpty()) null else item.removeAt(0)
    fun ukuran(): Int = item.size
}

// K, V: Key, Value
class PetaSederhana<K, V> {
    private val pasangan = mutableListOf<Pair<K, V>>()
    fun taruh(k: K, v: V) {
        pasangan.add(Pair(k, v))
    }
    fun ambil(k: K): V? = pasangan.firstOrNull { it.first == k }?.second
}

// R: Return type untuk function
fun <R> jalankan(pemasok: () -> R): R = pemasok()

// T dengan bound tunggal
class PenampungAngka<T : Number>(val nilai: T) {
    fun doubleNilai(): Double = nilai.toDouble() * 2
}

// T dengan recursive bound (Comparable<T>)
fun <T : Comparable<T>> urutkanDanAmbilMaks(a: T, b: T, c: T): T {
    val daftar = listOf(a, b, c).sorted()
    return daftar.last()
}

fun main() {
    val kotakStr = Kotak("Halo")
    println("Isi: ${kotakStr.ambil()}")

    val antrian = Antrian<String>()
    antrian.masuk("Budi")
    antrian.masuk("Siti")
    println("Keluar: ${antrian.keluar()}")
    println("Sisa: ${antrian.ukuran()}")

    val peta = PetaSederhana<String, Int>()
    peta.taruh("satu", 1)
    peta.taruh("dua", 2)
    println("Peta['dua'] = ${peta.ambil("dua")}")

    val hasil = jalankan { 40 + 2 }
    println("Hasil jalankan: $hasil")

    val penampung = PenampungAngka(10)
    println("Double: ${penampung.doubleNilai()}")

    val maks = urutkanDanAmbilMaks(3, 9, 5)
    println("Maks dari (3, 9, 5): $maks")

    val maksStr = urutkanDanAmbilMaks("Mangga", "Apel", "Pisang")
    println("Maks dari (Mangga, Apel, Pisang): $maksStr")
}

Output yang Dihasilkan:

text
Isi: Halo
Keluar: Budi
Sisa: 1
Peta['dua'] = 2
Hasil jalankan: 42
Double: 20.0
Maks dari (3, 9, 5): 9
Maks dari (Mangga, Apel, Pisang): Pisang

Mari kita telaah baris-baris kode di atas:

  1. class Kotak<T>(val isi: T): type parameter T tanpa bound. Kita bisa memasukkan tipe apa pun, namun tidak bisa memanggil method khusus seperti toDouble.
  2. class Antrian<E>: konvensi E untuk elemen koleksi. Method masuk, keluar, dan ukuran bekerja pada tipe E.
  3. class PetaSederhana<K, V>: dua type parameter, K untuk kunci dan V untuk nilai. Pola yang sama dipakai standard library pada Map<K, V>.
  4. fun <R> jalankan(pemasok: () -> R): R: type parameter R untuk tipe kembalian. Bound tidak diperlukan karena kita hanya meneruskan nilai dari pemasok.
  5. class PenampungAngka<T : Number>: bound tunggal T : Number. Setelah bound ini, kita dapat memanggil nilai.toDouble() di dalam method doubleNilai.
  6. fun <T : Comparable<T>> urutkanDanAmbilMaks: recursive bound. T harus subtype dari Comparable<T> sehingga method compareTo tersedia. Pemanggilan dengan Int dan String diterima karena keduanya mengimplementasikan Comparable.
  7. urutkanDanAmbilMaks("Mangga", "Apel", "Pisang"): kompilator menyimpulkan T = String. String dibandingkan secara lexicographic, jadi “Pisang” adalah yang terbesar.

Memilih Konvensi yang Tepat #

Saat menulis class atau function generic, ikuti konvensi yang sudah lazim agar kode konsisten dengan ekosistem. Saat membaca kode Kotlin di standard library atau library populer, kita akan menemui konvensi yang sama. Hal ini mempercepat pemahaman karena pembaca tidak perlu menebak makna setiap huruf.

Referensi Lanjutan #

  • Generics — penjelasan resmi type parameter T, E, K, V di Kotlin
  • Reified type parameters — keterhubungan type parameter dengan reified pada inline function

Pada chapter Upper Bounds kita akan membedah lebih jauh bagaimana bound dapat berupa lebih dari satu tipe dengan klausa where, serta implikasinya pada tipe yang boleh dipakai sebagai type argument.