Sealed Class

Hierarki terbatas dan when yang lengkap atas tipe.

Daftar isi

Sealed class adalah kelas yang pembatasan turunannya dinyatakan secara eksplisit, sehingga seluruh tipe yang mewarisinya sudah diketahui sejak kompilasi. Tidak ada kelas lain di luar daftar tersebut yang dapat menjadi turunannya. Topik ini membahas deklarasi sealed class dengan turunan data object dan data class, when yang mencakup seluruh cabang tanpa else, jaminan keamanan ketika hierarki bertambah, serta perbedaannya dengan enum class. Contoh yang dipakai adalah hasil ujian dengan kemungkinan lulus, remedial, dan gagal.

Hierarki yang terbatas#

Sealed class dinyatakan dengan kata kunci sealed dan langsung diikuti daftar turunan yang diizinkan. Turunan dapat berupa kelas biasa, data class, maupun data object untuk varian yang tidak membawa data. Sejak Kotlin 1.5, seluruh turunan wajib berada dalam package dan module yang sama dengan sealed class-nya, tetapi boleh ditulis di berkas yang berbeda. Sealed class tidak dapat dibuat instansnya secara langsung karena konstruktornya bersifat protected, sehingga nilai bertipe ini selalu berupa salah satu variannya. Bentuk lain dengan aturan serupa adalah sealed interface, yaitu interface yang daftar implementasinya juga dibatasi.

Program berikut mendefinisikan hierarki HasilUjian dengan tiga varian. Lulus membawa nilai akhir, Remedial tidak membawa data apa pun, dan Gagal membawa alasan kegagalannya. Ketiganya dinyatakan sebagai turunan langsung di dalam badan sealed class-nya.

kotlin main.kt
sealed class HasilUjian {
    data class Lulus(val nilai: Int) : HasilUjian()
    data object Remedial : HasilUjian()
    data class Gagal(val alasan: String) : HasilUjian()
}

fun ringkas(hasil: HasilUjian): String = when (hasil) {
    is HasilUjian.Lulus -> "Lulus dengan nilai ${hasil.nilai}"
    HasilUjian.Remedial -> "Diwajibkan mengikuti remedial"
    is HasilUjian.Gagal -> "Gagal: ${hasil.alasan}"
}

fun main() {
    val daftar = listOf(
        HasilUjian.Lulus(88),
        HasilUjian.Remedial,
        HasilUjian.Gagal("kehadiran kurang dari 75 persen"),
    )
    for (hasil in daftar) {
        println(ringkas(hasil))
    }

    println(HasilUjian.Remedial)
}
bash
# atur PATH compiler Kotlin dan lokasi kotlin-stdlib
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 main.kt menjadi out.jar
kotlinc main.kt -d out.jar
# jalankan program dengan kotlin-stdlib di classpath
java -cp "out.jar:$STDLIB" MainKt
text Keluaran
Lulus dengan nilai 88
Diwajibkan mengikuti remedial
Gagal: kehadiran kurang dari 75 persen
Remedial

Keluaran menunjukkan tiga bentuk penanganan yang berbeda atas satu tipe yang sama. Lulus(88) diproses dengan membaca properti nilai, sedangkan Gagal membaca alasan. Varian Remedial dicocokkan langsung dengan konstantanya karena data object hanya memiliki satu instans, dan pencetakan langsungnya menghasilkan nama Remedial saja, bukan teks berisi alamat memori.

when yang lengkap atas tipe#

Fungsi ringkas pada program di atas memperlihatkan when atas tipe sealed. Setiap cabang mencocokkan satu varian, yaitu is HasilUjian.Lulus untuk varian yang membawa data, HasilUjian.Remedial untuk data object, dan is HasilUjian.Gagal untuk varian terakhir. Varian data object dicocokkan tanpa kata kunci is karena pembandingannya adalah kesamaan nilai terhadap satu-satunya instans, bukan pengujian tipe. Kata kunci is menguji tipe sekaligus melakukan konversi cerdas (smart cast), sehingga properti nilai dan alasan dapat dibaca tanpa penulisan cast eksplisit.

when untuk tipe sealed tidak memerlukan cabang else. Compiler mengetahui seluruh varian yang mungkin dan menuntut setiap varian mendapat penanganan. Ketidaklengkapan dilaporkan sebagai error baik ketika when dipakai sebagai ekspresi maupun sebagai pernyataan, sehingga hasil when dapat diamanatkan langsung sebagai nilai kembalian fungsi seperti pada ringkas. Percabangan berdasarkan tipe seperti ini menggantikan rantai if panjang yang memeriksa varian satu per satu.

Saat hierarki bertambah#

Keunggulan utama sealed class terasa ketika varian baru ditambahkan. Misalkan hierarki diperluas dengan data object Tunda, sedangkan fungsi ringkas belum diperbarui.

kotlin Contoh: when yang kurang satu cabang atas sealed class
// tidak-dijalankan
fun ringkas(hasil: HasilUjian): String = when (hasil) {
    is HasilUjian.Lulus -> "Lulus dengan nilai ${hasil.nilai}"
    is HasilUjian.Gagal -> "Gagal: ${hasil.alasan}"
}

Kode tersebut gagal dikompilasi dengan pesan 'when' expression must be exhaustive. Add the 'Remedial', 'Tunda' branches or an 'else' branch. Compiler bahkan menyebutkan cabang yang masih kurang. Pemeliharaan menjadi terarah karena setiap penambahan varian memaksa seluruh titik penanganan diperiksa ulang.

Bandingkan dengan when yang ditutup else. Varian baru lolos kompilasi tanpa peringatan, lalu jatuh diam-diam ke cabang else dan ditangani logika lama yang belum tentu tepat. Kesalahan semacam ini sulit dilacak karena tidak pernah muncul sebagai error. Aturan penagihan ini berlaku untuk setiap berkas yang memakai tipe tersebut, bukan hanya berkas tempat hierarkinya dinyatakan. Menahan diri dari else pada when atas sealed class merupakan kebiasaan yang menyelamatkan.

Sealed class versus enum class#

Enum class dan sealed class sama-sama membatasi kemungkinan nilai, dan keduanya mendukung when yang lengkap tanpa else. Perbedaannya terletak pada apa yang dibatasi. Enum membatasi nilai, yaitu sekumpulan konstanta tunggal yang masing-masing berupa satu instans dengan bentuk data yang sama. Sealed class membatasi tipe, yaitu sekumpulan kelas turunan yang tiap anggotanya bebas membawa properti berbeda dan dapat memiliki banyak instans.

AspekEnum classSealed class
Yang dibatasiHimpunan konstanta bernamaHimpunan tipe turunan
Jumlah instansSatu instans per konstantaBebas, tiap varian dapat banyak instans
Bentuk dataSama untuk semua konstantaBerbeda antar turunan
Letak deklarasiLangsung di badan kelasDalam package dan module yang sama

Contoh pada topik ini memperlihatkan kontrasnya. Hasil ujian membawa data yang berbeda antarvarian, sehingga sealed class lebih tepat. Sebaliknya, nilai yang tidak membawa data berbeda antaranggotanya, seperti arah mata angin, cukup dinyatakan sebagai enum class. Pemilihannya menjadi sederhana, yaitu enum untuk himpunan nilai yang seragam bentuk datanya, dan sealed class untuk varian yang tiap anggotanya membawa data berbeda.

Ringkasan#

  • sealed membatasi hierarki pada turunan yang dideklarasikan dalam package dan module yang sama.
  • when atas tipe sealed wajib mencakup seluruh varian dan dapat ditulis tanpa else, baik sebagai ekspresi maupun pernyataan.
  • Penambahan varian baru ditagih compiler melalui error kompilasi, sehingga seluruh titik penanganan ikut diperiksa ulang.
  • Enum membatasi nilai dengan bentuk data yang sama, sedangkan sealed class membatasi tipe yang bebas membawa data berbeda.

Referensi#