Fungsi extension menambahkan perilaku baru pada sebuah tipe yang sudah ada tanpa membuat kelas turunan maupun menyunting kelas asalnya. Deklarasi ditulis di luar kelas tersebut dengan menyebut tipe penerima (receiver) di depan nama fungsi, lalu fungsi itu dipanggil seolah-olah anggota asli tipenya. Topik ini membahas cara mendeklarasikan fungsi extension, extension pada tipe nullable, serta mekanisme penyelesaiannya yang bersifat statis. Seluruh contoh dijalankan dengan compiler Kotlin sehingga keluarannya berasal dari eksekusi nyata.
Mendeklarasikan fungsi extension#
Bentuk deklarasinya adalah fun Tipe.namaFungsi() dengan nama tipe ditulis sebelum titik. Tipe di depan titik itu disebut receiver, dan di dalam badan fungsi, this merujuk pada objek penerima tersebut. Fungsi extension tidak menyuntikkan anggota baru ke dalam kelasnya. Compiler menerjemahkannya menjadi fungsi biasa yang menerima objek penerima sebagai argumen pertama, sehingga badan fungsi hanya dapat mengakses anggota yang terlihat dari luar kelas, bukan anggota private.
Dua fungsi extension pada program berikut dideklarasikan di luar kelas mana pun. Fungsi hitungKata ditambahkan pada String, sedangkan rataRata ditambahkan pada List<Int>. Keduanya dipanggil seperti metode bawaan tipe masing-masing, dan programnya juga memuat extension pada tipe nullable yang dibahas pada bagian berikutnya.
fun String.hitungKata(): Int =
trim().split(Regex("\\s+")).size
fun List<Int>.rataRata(): Double =
if (isEmpty()) 0.0 else sum().toDouble() / size
fun String?.bilaKosong(fallback: String): String =
if (isNullOrEmpty()) fallback else this
fun main() {
val kalimat = " Kotlin membuat kode lebih ekspresif "
println(kalimat.hitungKata())
println(kalimat.length)
val nilai = listOf(80, 65, 90, 55, 75)
println(nilai.rataRata())
val pencatat: String? = null
println(pencatat.bilaKosong("(tanpa pencatat)"))
val judul: String? = "Sensor Suhu Ruang Kerja"
println(judul.bilaKosong("(tanpa pencatat)"))
}# 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" MainKt5
39
73.0
(tanpa pencatat)
Sensor Suhu Ruang KerjaFungsi hitungKata menghitung lima kata pada kalimat tersebut dengan memotong spasi di ujungnya terlebih dahulu, lalu memecah sisanya pada setiap rangkaian spasi. Pemanggilan kalimat.length tetap menghasilkan 39 karena extension tidak pernah mengubah isi string, dan kelas String juga tidak tersentuh sama sekali. Fungsi rataRata menghasilkan 73.0 dari lima nilai ujian tersebut, dan tidak ada satu pun kelas turunan yang dideklarasikan untuk memperoleh kedua perilaku itu.
Extension pada tipe nullable#
Receiver sebuah extension boleh bertipe nullable, misalnya String?. Bentuk ini berguna untuk perilaku yang harus tetap dapat dipanggil meskipun nilai penerimanya null. Di dalam fungsi bilaKosong, this bertipe String?, dan pemeriksaan isNullOrEmpty() menentukan apakah nilai penerima dikembalikan atau diganti dengan cadangannya. Dua pemanggilan pada keluaran di atas memperlihatkan kedua jalurnya. Variabel pencatat bernilai null sehingga hasilnya “(tanpa pencatat)”, sedangkan judul terisi sehingga nilainya dikembalikan apa adanya.
Tanpa receiver nullable, setiap titik pemanggilan wajib memeriksa null terlebih dahulu sebelum fungsinya dipanggil. Dengan receiver nullable, pemeriksaan itu berpindah ke dalam satu tempat, yaitu badan extensionnya, sehingga titik pemanggilan tetap ringkas. Pemanggilan langsung tanpa safe call pun sah, karena fungsinya sendiri yang menerima kemungkinan nilai null. Pola ini banyak dipakai pustaka standar. Fungsi isNullOrEmpty() yang dipanggil di dalam bilaKosong sendiri adalah extension pada tipe nullable CharSequence?, sehingga bilaKosong dapat memanggilnya langsung pada this yang bertipe String?.
Perlu dicatat bahwa sifat nullable ini melekat pada setiap extension secara terpisah. Extension yang dideklarasikan pada String tidak dapat dipanggil pada nilai String?, sedangkan extension yang dideklarasikan pada String? dapat dipanggil pada keduanya. Pustaka standar menyediakan banyak extension nullable, misalnya orEmpty() yang mengubah nilai String? menjadi string kosong, sehingga penanganan null yang serupa dapat dipinjam tanpa menulis ulang.
Penyelesaian statis, bukan polymorphic#
Extension diselesaikan pada saat kompilasi berdasarkan tipe yang dideklarasikan pada variabel, bukan berdasarkan tipe objek yang sebenarnya saat program berjalan. Sifat ini sering disalahpahami sebagai perilaku polymorphic, padahal keduanya berbeda. Contoh berikut memakai satu kelas induk, satu kelas anak, dan dua fungsi extension dengan nama sama pada keduanya.
open class Sensor
class SensorSuhu : Sensor()
fun Sensor.label(): String = "sensor umum"
fun SensorSuhu.label(): String = "sensor suhu"
fun main() {
val tercatat: Sensor = SensorSuhu()
println(tercatat.label())
println(SensorSuhu().label())
}# kompilasi berkas statis.kt menjadi statis.jar, lalu jalankan programnya
kotlinc statis.kt -d statis.jar && java -cp "statis.jar:$STDLIB" StatisKtsensor umum
sensor suhuVariabel tercatat bertipe Sensor, sehingga compiler memilih extension label milik Sensor meskipun objek yang disimpannya adalah SensorSuhu. Hasilnya “sensor umum”, sedangkan pemanggilan pada variabel yang bertipe SensorSuhu langsung menghasilkan “sensor suhu”. Pemilihan terjadi sekali pada saat kompilasi dan tidak berubah saat program berjalan.
Apabila perilaku yang mengikuti tipe objek diinginkan, gunakan pewarisan dengan fungsi open yang di-override oleh kelas turunannya. Extension lebih cocok untuk perilaku tambahan yang berdiri sendiri dan tidak dimaksudkan untuk menggantikan perilaku dasar suatu hierarki kelas.
Ringkasan#
- Fungsi extension dideklarasikan dengan menulis tipe receiver sebelum nama fungsinya, lalu dipanggil seperti anggota asli tipe tersebut.
- Extension tidak menyunting kelas asalnya, tidak menambahkan anggota baru ke dalamnya, dan hanya dapat mengakses anggota yang terlihat dari luar kelas.
- Receiver boleh bertipe nullable, sehingga penanganan null dapat dipindahkan ke dalam satu extension.
- Extension diselesaikan secara statis dari tipe deklarasi variabel dan tidak berperilaku polymorphic.
Referensi#
- Extensions — Kotlin Documentation — Deklarasi fungsi dan properti extension, peran receiver, serta penyelesaiannya secara statis.