Pewarisan dan open

Kata kunci open yang eksplisit, override, dan super.

Daftar isi

Pewarisan (inheritance) membentuk relasi antara kelas induk dan kelas turunan, di mana turunan menerima anggota induknya dan berhak menimpa sebagian perilakunya. Kotlin menutup setiap kelas dan anggotanya secara bawaan, sehingga pewarisan hanya terjadi apabila kelas induk menyatakan dirinya open. Topik ini membahas error yang muncul ketika kelas non-open diturunkan, cara membuka kelas dan anggota dengan open, penimpaan dengan override, serta pemanggilan implementasi induk melalui super.

Kelas tertutup secara bawaan#

Kelas di Kotlin menyerupai kelas yang diberi kata kunci final pada Java, yaitu tidak dapat diturunkan kecuali dinyatakan lain. Keputusan desain ini disengaja, karena pewarisan yang terbuka tanpa rancangan membuat perubahan kecil pada kelas induk dapat merusak turunan-turunannya di luar kendali. Dengan penutupan bawaan, kata open pada sebuah kelas menjadi pernyataan bahwa kelas itu memang dirancang untuk diturunkan.

Akibat langsungnya terlihat ketika kelas biasa dicoba diturunkan. Berkas berikut sengaja memuat kesalahan tersebut.

kotlin turunansalah.kt
class KendaraanDinas(val nomorPolisi: String)

class MobilDinas(nomorPolisi: String) : KendaraanDinas(nomorPolisi)
bash
# kompilasi berkas turunansalah.kt untuk melihat pesan error dari compiler
kotlinc turunansalah.kt -d salah.jar
text Keluaran
turunansalah.kt:3:41: error: this type is final, so it cannot be extended.
class MobilDinas(nomorPolisi: String) : KendaraanDinas(nomorPolisi)
                                        ^^^^^^^^^^^^^^

Pesan this type is final, so it cannot be extended menyebut posisi tepat penulisan tipe induknya. Perbaikannya adalah menambahkan open di depan deklarasi KendaraanDinas, bukan mengubah kelas turunannya. Aturan yang sama berlaku dua arah, yaitu kelas induk harus open dan anggota yang akan ditimpa juga harus open.

Penutupan bawaan juga memengaruhi cara membaca kode. Kelas tanpa kata open menjanjikan bahwa implementasinya tidak akan diganti oleh turunan, sehingga pemanggilan anggotanya dapat dianalisis dengan pasti. Sebaliknya, bertemunya kata open pada sebuah kelas adalah pernyataan bahwa kelas itu dirancang untuk diturunkan, lengkap dengan kewajiban menjaga kontrak anggota-anggota yang dibukanya.

Kata kunci open dan override#

Kata kunci open ditempatkan di depan deklarasi kelas untuk membuka pewarisan, dan di depan deklarasi fungsi atau properti untuk mengizinkan penimpaan. Kelas turunan ditulis dengan tanda titik dua diikuti nama kelas induk beserta argumen constructornya, sebagaimana KendaraanDinas(nomorPolisi) pada contoh berikut.

kotlin main.kt
open class KendaraanDinas(val nomorPolisi: String) {
    open fun deskripsi(): String {
        return "Kendaraan dinas $nomorPolisi"
    }
}

class MobilDinas(nomorPolisi: String, val jumlahKursi: Int) : KendaraanDinas(nomorPolisi) {
    override fun deskripsi(): String {
        val dasar = super.deskripsi()
        return "$dasar dengan $jumlahKursi kursi"
    }
}

class SepedaDinas(nomorPolisi: String) : KendaraanDinas(nomorPolisi) {
    override fun deskripsi(): String {
        return "Sepeda dinas $nomorPolisi"
    }
}

fun main() {
    val armada: List<KendaraanDinas> = listOf(
        MobilDinas("AB 101", 4),
        SepedaDinas("AB 202")
    )
    for (kendaraan in armada) {
        println(kendaraan.deskripsi())
    }
}
bash
# 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" MainKt
text Keluaran
Kendaraan dinas AB 101 dengan 4 kursi
Sepeda dinas AB 202

Setiap kelas turunan memberi versi deskripsi miliknya sendiri melalui override, dan kata kunci tersebut wajib ditulis, tidak boleh diam-diam seperti pada Java. Variabel armada bertipe List<KendaraanDinas> tetapi menyimpan dua turunan yang berbeda, dan pemanggilan deskripsi() pada setiap elemen menjalankan versi milik instansnya, bukan versi milik tipe variabelnya. Perilaku ini disebut pengiriman dinamis (dynamic dispatch), dan inilah alasan keluaran memuat dua kalimat yang berbeda dari satu pemanggilan yang sama bentuknya.

Pembentukan instans turunan juga menjalankan constructor induk terlebih dahulu, karena induknya harus selesai diinisialisasi sebelum tubuh turunan dieksekusi. Properti nomorPolisi tidak pernah dideklarasikan ulang di MobilDinas maupun SepedaDinas, tetapi kedua kelas itu tetap memilikinya sebagai warisan dari induk. Karena properti tersebut bersifat public, kedua turunan bebas membacanya, sedangkan anggota yang hanya untuk kepentingan turunan sebaiknya diberi protected agar tidak terbuka bagi kode lain.

Penimpaan juga berlaku pada properti, yaitu properti open pada kelas induk boleh ditimpa dengan override pada kelas turunan, dengan syarat tipenya sekompatibel. Properti turunan bahkan boleh menimpa properti val milik induk dengan var miliknya, karena menyediakan setter merupakan peluasan, bukan pelanggaran kontrak.

Memanggil implementasi induk dengan super#

Kata kunci super mengarahkan pemanggilan ke implementasi milik kelas induk. Pada MobilDinas, super.deskripsi() mengambil kalimat bawaan dari KendaraanDinas, lalu hasilnya diperkaya dengan jumlah kursi. Pola ini menghindari penyalinan teks yang sama di banyak kelas turunan, sehingga perubahan kalimat pada induk otomatis terbawa ke turunan yang memakainya.

Pemanggilan super hanya bermakna di dalam penimpaan yang memang menyambung ke induk. Apabila implementasi turunan sepenuhnya menggantikan perilaku induk, sebagaimana deskripsi milik SepedaDinas, pemanggilan super tidak diperlukan dan boleh ditiadakan. Kedua gaya pada program di atas menunjukkan bahwa pilihan antara memperkaya dan menggantikan sepenuhnya diserahkan kepada kebutuhan masing-masing turunan. Kata super juga berlaku pada penimpaan properti, sehingga getter milik turunan dapat membaca nilai induk sebelum melengkapinya.

Kesalahan umum#

Kesalahan pertama adalah menimpa anggota induk tanpa memberi override, dan compiler menolaknya karena nama tersebut dianggap anggota baru yang kebetulan sama. Kesalahan kedua adalah memberi override pada anggota induk yang tidak diberi open, dan pesan errornya menyatakan bahwa anggota di induk masih final. Perbaikan keduanya berada di kelas induk, yaitu menambahkan open pada anggota yang memang dirancang untuk ditimpa.

Kesalahan ketiga adalah lupa meneruskan argumen constructor induk. Penulisan : KendaraanDinas() tanpa argumen hanya sah apabila induknya memiliki constructor tanpa parameter, sedangkan KendaraanDinas membutuhkan nomorPolisi. Karena constructor utama MobilDinas menerima nomorPolisi sebagai parameter biasa, nilai itu diteruskan ke induk pada bagian : KendaraanDinas(nomorPolisi).

Ringkasan#

  • Kelas dan anggota Kotlin bersifat final secara bawaan, sehingga pewarisan memerlukan open secara eksplisit.
  • Kelas turunan ditulis dengan tanda titik dua diikuti nama dan argumen constructor induknya.
  • override wajib ditulis pada setiap penimpaan, dan anggota yang ditimpa juga harus open.
  • super memanggil implementasi induk dari dalam penimpaan, dan pemanggilan deskripsi() pada variabel bertipe induk menjalankan versi milik instansnya.

Referensi#