Generics memungkinkan satu definisi class bekerja untuk berbagai tipe data tanpa kita menulis ulang kode yang sama. Bayangkan kita ingin membuat class wadah yang bisa menyimpan Int, String, atau Pengguna — tanpa generics, kita harus menulis tiga class berbeda atau memakai Any dan kehilangan type safety pada saat kompilasi. Dengan generics, kita menulis satu definisi class Box<T> dan kompilator tetap mengetahui tipe setiap instance. Pada chapter ini kita akan membedah cara mendeklarasikan class generic, menginstantiasinya dengan tipe konkret, serta memakai lebih dari satu type parameter dalam satu class.
Apa Itu Type Parameter #
Type parameter adalah placeholder tipe yang ditulis di dalam tanda kurung siku setelah nama class. Kompilator akan mengganti placeholder tersebut dengan tipe konkret saat kita menginstantiasi class. Konvensi penamaan type parameter memakai huruf kapital tunggal seperti T untuk Type, E untuk Element, atau K dan V untuk Key dan Value.
Bentuk paling sederhana adalah class Box<T>(val value: T). Tanda T di sini berfungsi sebagai placeholder yang akan diisi saat deklarasi variabel. Kompilator memverifikasi setiap akses value agar sesuai dengan tipe yang kita berikan, sehingga kode yang mencampur Box<Int> dengan Box<String> akan ditolak sebelum program dijalankan.
class Box<T>(val value: T)Membuat dan Menggunakan Box #
Sekarang mari kita lihat class Box lengkap dengan beberapa method, lalu instantiasi dengan tipe yang berbeda:
// Chapter: Generic Classes
// Mendefinisikan class generic dengan satu dan beberapa type parameter.
class Box<T>(val value: T) {
fun deskripsi(): String = "Box berisi nilai $value bertipe ${value!!::class.simpleName}"
}
class Pair<A, B>(val first: A, val second: B) {
fun tukar(): Pair<B, A> = Pair(second, first)
}
class Tumpukan<T> {
private val penyimpanan = mutableListOf<T>()
fun dorong(item: T) {
penyimpanan.add(item)
}
fun ambil(): T? = if (penyimpanan.isEmpty()) null else penyimpanan.removeAt(penyimpanan.lastIndex)
fun ukuran(): Int = penyimpanan.size
}
fun main() {
val kotakInt = Box<Int>(5)
println(kotakInt.value)
println(kotakInt.deskripsi())
val kotakString = Box("Halo Kotlin")
println(kotakString.value)
println(kotakString.deskripsi())
val pasangan = Pair<String, Int>("Budi", 25)
println("First: ${pasangan.first}, Second: ${pasangan.second}")
val ditukar = pasangan.tukar()
println("Setelah ditukar -> First: ${ditukar.first}, Second: ${ditukar.second}")
val tumpukanInt = Tumpukan<Int>()
tumpukanInt.dorong(10)
tumpukanInt.dorong(20)
tumpukanInt.dorong(30)
println("Ukuran tumpukan: ${tumpukanInt.ukuran()}")
println("Ambil: ${tumpukanInt.ambil()}")
println("Ambil: ${tumpukanInt.ambil()}")
println("Ambil: ${tumpukanInt.ambil()}")
println("Ambil dari tumpukan kosong: ${tumpukanInt.ambil()}")
}Output yang Dihasilkan:
5
Box berisi nilai 5 bertipe Int
Halo Kotlin
Box berisi nilai Halo Kotlin bertipe String
First: Budi, Second: 25
Setelah ditukar -> First: 25, Second: Budi
Ukuran tumpukan: 3
Ambil: 30
Ambil: 20
Ambil: 10
Ambil dari tumpukan kosong: nullMari kita telaah baris-baris kode di atas:
class Box<T>(val value: T):Tadalah type parameter. Konstruktor menerima satu argumenvaluebertipeT. Saat kita menulisBox<Int>(5), kompilator mengikatTkeInt, sehinggavaluebertipeInt.fun deskripsi(): method ini memakai propertivalueyang bertipeT. Ekspresivalue!!::class.simpleNamemengambil nama kelas runtime dari nilai, sehingga pesan yang dicetak informatif untuk setiap tipe.class Pair<A, B>(val first: A, val second: B): class generic dengan dua type parameter. Hal ini berguna ketika kita ingin membungpas dua nilai berbeda tipe sepertiStringdanInt.fun tukar(): Pair<B, A>: method yang mengembalikan instancePairbaru dengan tipe parameter terbalik. Perhatikan bahwa tipe kembalian adalahPair<B, A>, bukanPair<A, B>.class Tumpukan<T>: class generic tanpa argumen konstruktor. Type parameterTdipakai di methoddorong(item: T)danambil(): T?sehingga kita bisa membuat tumpukan untuk tipe apa pun.val tumpukanInt = Tumpukan<Int>(): kita menentukan tipe eksplisitIntsaat instantiasi. Kompilator memverifikasi bahwa hanyaIntyang boleh dimasukkan ke dalam tumpukan ini.Box("Halo Kotlin"): tanpa menulisBox<String>, kompilator menyimpulkan type parameter dari argumen yang diberikan. Hal ini disebut type inference dan membuat kode lebih ringkas.
Beberapa Type Parameter Sekaligus #
Class generic tidak terbatas pada satu type parameter. Pair<A, B> di atas adalah contoh sederhana. Pola yang sama dipakai oleh Map<K, V> di standard library Kotlin, di mana K untuk tipe kunci dan V untuk tipe nilai. Kita juga bisa membuat class dengan tiga atau lebih type parameter, namun semakin banyak type parameter, semakin sulit kode dibaca.
Mengapa Generic Class Penting #
Tanpa generics, kita akan terpaksa memakai Any dan melakukan cast manual setiap kali mengakses nilai. Pendekatan tersebut rentan galat runtime karena cast bisa gagal. Dengan generic class, kompilator melakukan verifikasi tipe saat kompilasi sehingga banyak galat tertangkap lebih awal. Hal ini menjadi fondasi penting sebelum kita melangkah ke generic functions yang akan kita bahas pada chapter berikutnya.
Referensi Lanjutan #
- Generics
— panduan resmi deklarasi generic class dengan type parameter
T - Any API reference
— API reference
Anysebagai supertype yang dipakai bila generics tidak tersedia
Pada chapter Generic Functions kita akan melihat bagaimana ide yang sama dapat diterapkan pada level function, sehingga satu definisi function dapat menerima dan mengembalikan tipe apa pun dengan tetap menjaga type safety.