Pada chapter sebelumnya kita melihat bahwa sealed class membatasi subtype pada daftar yang dideklarasikan di file yang sama. Kotlin juga mendukung sealed interface — interface dengan batasan serupa: daftar class yang mengimplementasikannya harus berada di file yang sama. Sealed interface berguna ketika kita ingin beberapa subtype berbagi kontrak namun tidak ingin mengikatnya pada satu class abstract — sebuah class dapat mengimplementasikan beberapa sealed interface sekaligus, dan sealed interface dapat dijadikan subtype dari sealed interface lain. Pada chapter ini kita akan mendeklarasikan sealed interface, mengkombinasikannya dengan sealed class, dan memakai pola multiple interface implementation pada satu class.

Apa Itu Sealed Interface #

sealed interface adalah interface yang dideklarasikan dengan keyword sealed. Semua class yang langsung mengimplementasikannya harus berada di file yang sama (atau package yang sama sejak Kotlin 1.5). Karena daftar implementor terbatas dan dikenal kompilator, when pada sealed interface bersifat exhaustive — semua cabang harus ditangani tanpa perlu else.

Keuntungan sealed interface dibanding sealed class adalah fleksibilitas multiple inheritance. Sebuah class Kotlin hanya dapat mewarisi satu superclass (sekalipun sealed), namun dapat mengimplementasikan banyak interface. Dengan sealed interface, sebuah class dapat mengimplementasikan beberapa sealed interface sekaligus — sesuatu yang tidak mungkin dengan sealed class tunggal.

Sealed Interface untuk Hierarki Peristiwa #

Contoh berikut mendefinisikan sealed interface Peristiwa, lalu membuat beberapa subtype dengan struktur berbeda — KlikTombol, GulirLayar, AplikasiDimulai. Contoh juga menunjukkan sealed interface BentukGeometri dengan sealed class BangunDatar sebagai subtype, dan pola multiple interface implementation pada class CatatanPeristiwa yang mengimplementasikan dua sealed interface sekaligus.

kotlin
// sealed_interfaces.kt
// Mendemonstrasikan `sealed interface` Kotlin — restricted interface
// hierarchy yang dapat dikombinasikan dengan sealed class.

sealed interface Peristiwa {
    val waktu: String
}

sealed interface Persisten {
    fun simpan(): String
}

// Subtype berupa class biasa, data class, atau object.
data class KlikTombol(val tombol: String, override val waktu: String) : Peristiwa
data class GulirLayar(val jarak: Int, override val waktu: String) : Peristiwa
object AplikasiDimulai : Peristiwa {
    override val waktu: String = "00:00"
}

// Sealed interface dengan sealed class sebagai subtype.
sealed interface BentukGeometri {
    fun luas(): Double
}

sealed class BangunDatar : BentukGeometri {
    data class Lingkaran(private val r: Double) : BangunDatar() {
        override fun luas(): Double = Math.PI * r * r
    }
    data class Persegi(private val sisi: Double) : BangunDatar() {
        override fun luas(): Double = sisi * sisi
    }
    data class Segitiga(private val alas: Double, private val tinggi: Double) : BangunDatar() {
        override fun luas(): Double = 0.5 * alas * tinggi
    }
}

// Sealed interface yang mewarisi sealed interface lain.
sealed interface PeristiwaUI : Peristiwa {
    val komponen: String
}

data class KlikKomponen(
    override val komponen: String,
    override val waktu: String,
) : PeristiwaUI

data class FokusKomponen(
    override val komponen: String,
    override val waktu: String,
) : PeristiwaUI

// Class yang mengimplementasikan beberapa sealed interface sekaligus.
data class CatatanPeristiwa(
    override val waktu: String,
    val pesan: String,
) : Peristiwa, Persisten {
    override fun simpan(): String = "Catatan[${waktu}]: $pesan disimpan ke database"
}

fun ringkas(p: Peristiwa): String = when (p) {
    is KlikTombol -> "[${p.waktu}] Klik tombol: ${p.tombol}"
    is GulirLayar -> "[${p.waktu}] Gulir sejauh ${p.jarak} px"
    AplikasiDimulai -> "[${p.waktu}] Aplikasi dimulai"
    is KlikKomponen -> "[${p.waktu}] Klik komponen: ${p.komponen}"
    is FokusKomponen -> "[${p.waktu}] Fokus komponen: ${p.komponen}"
    is CatatanPeristiwa -> "[${p.waktu}] Catatan: ${p.pesan}"
}

fun ringkasBentuk(b: BentukGeometri): String = when (b) {
    is BangunDatar.Lingkaran -> "Lingkaran, luas = ${b.luas()}"
    is BangunDatar.Persegi -> "Persegi, luas = ${b.luas()}"
    is BangunDatar.Segitiga -> "Segitiga, luas = ${b.luas()}"
}

fun main() {
    val peristiwa: List<Peristiwa> = listOf(
        AplikasiDimulai,
        KlikTombol("Simpan", "00:05"),
        GulirLayar(120, "00:08"),
        KlikKomponen("tombolBatal", "00:12"),
        FokusKomponen("inputNama", "00:15"),
        CatatanPeristiwa("00:20", "Peristiwa aneh"),
    )
    println("Daftar peristiwa:")
    for (p in peristiwa) {
        println("  ${ringkas(p)}")
    }
    println()

    val bentuk: List<BentukGeometri> = listOf(
        BangunDatar.Lingkaran(2.0),
        BangunDatar.Persegi(3.0),
        BangunDatar.Segitiga(4.0, 5.0),
    )
    println("Daftar bentuk:")
    for (b in bentuk) {
        println("  ${ringkasBentuk(b)}")
    }
    println()

    val catatan: Any = CatatanPeristiwa("00:30", "Pengguna Budi menyimpan laporan")
    // Simpan sebagai Any agar pemeriksaan `is` menjadi informatif.
    println("Catatan implements Peristiwa? ${catatan is Peristiwa}")
    println("Catatan implements Persisten? ${catatan is Persisten}")
    if (catatan is Persisten) {
        println(catatan.simpan())
    }
}

Output yang Dihasilkan:

text
Daftar peristiwa:
  [00:00] Aplikasi dimulai
  [00:05] Klik tombol: Simpan
  [00:08] Gulir sejauh 120 px
  [00:12] Klik komponen: tombolBatal
  [00:15] Fokus komponen: inputNama
  [00:20] Catatan: Peristiwa aneh

Daftar bentuk:
  Lingkaran, luas = 12.566370614359172
  Persegi, luas = 9.0
  Segitiga, luas = 10.0

Catatan implements Peristiwa? true
Catatan implements Persisten? true
Catatan[00:30]: Pengguna Budi menyimpan laporan disimpan ke database

Mari kita bedah kode di atas:

  1. sealed interface Peristiwa { val waktu: String }: sealed interface dengan satu property abstrak. Semua class yang mengimplementasikan Peristiwa harus berada di file yang sama dan menyediakan property waktu.
  2. data class KlikTombol(val tombol: String, override val waktu: String) : Peristiwa: subtype Peristiwa berupa data class. Property waktu di-override dari interface — override val wajib karena property tersebut bersifat abstrak di interface.
  3. object AplikasiDimulai : Peristiwa { override val waktu: String = "00:00" }: subtype berupa object. Object singleton juga dapat mengimplementasikan sealed interface. Karena object tidak punya constructor, property waktu diinisialisasi langsung dengan literal.
  4. sealed interface BentukGeometri { fun luas(): Double }; sealed class BangunDatar : BentukGeometri { ... }: sealed interface BentukGeometri diimplementasikan oleh sealed class BangunDatar. Pola bertingkat ini membuat hierarki dua tingkat — BentukGeometri sebagai kontrak umum, BangunDatar sebagai grup subtype dengan subhierarki sendiri. Function ringkasBentuk cukup memproses tiga subtype langsung BangunDatar — kompilator tetap menganggap when exhaustive karena tidak ada subtype BentukGeometri lain di luar BangunDatar.
  5. sealed interface PeristiwaUI : Peristiwa { val komponen: String }: sealed interface yang mewarisi sealed interface lain. Subtype PeristiwaUI otomatis menjadi subtype Peristiwa juga — KlikKomponen dan FokusKomponen dapat diperlakukan sebagai Peristiwa dan muncul dalam daftar List<Peristiwa>.
  6. data class CatatanPeristiwa(...) : Peristiwa, Persisten { override fun simpan(): String = ... }: satu class mengimplementasikan dua sealed interface sekaligus. Keuntungan sealed interface atas sealed class — class tidak dibatasi satu superclass. Instance CatatanPeristiwa dapat diperlakukan sebagai Peristiwa (mengakses waktu) maupun Persisten (memanggil simpan()).
  7. when (p) { is KlikTombol -> ...; ...; is CatatanPeristiwa -> ... }: function ringkas menerima Peristiwa (interface sealed). when menangani semua subtype — KlikTombol, GulirLayar, AplikasiDimulai, KlikKomponen, FokusKomponen, dan CatatanPeristiwa. Tidak perlu else karena daftar lengkap.
  8. catatan is Peristiwa, catatan is Persisten: dua pemeriksaan tipe pada instance yang sama. Karena CatatanPeristiwa mengimplementasikan kedua interface, kedua pemeriksaan menghasilkan true.

Kombinasi dengan Sealed Class #

Sealed interface dan sealed class sering dipakai bersama. Sebuah sealed interface dapat memiliki beberapa sealed class sebagai subtype, lalu masing-masing sealed class tersebut memiliki subhierarkinya sendiri. Pola bertingkat ini sangat berguna untuk memodelkan domain yang kompleks — misalnya sebuah aplikasi memiliki banyak jenis peristiwa yang dikelompokkan menjadi beberapa kategori besar, dan setiap kategori memiliki varian spesifik.

Sealed Interface Bertingkat #

Sealed interface dapat mewarisi sealed interface lain — misalnya PeristiwaUI : Peristiwa. Semua subtype PeristiwaUI otomatis menjadi subtype Peristiwa juga. Pola ini memungkinkan pembuatan kategori khusus yang lebih sempit tanpa menduplikasi kontrak dasar. Function yang menerima Peristiwa dapat memproses semua subtype — baik yang langsung mengimplementasikan Peristiwa maupun yang mengimplementasikan PeristiwaUI.

Referensi Lanjutan #

Ringkasan #

sealed interface membatasi daftar class yang mengimplementasikannya pada file yang sama. Sealed interface dapat dikombinasikan dengan sealed class, dijadikan subtype dari sealed interface lain, dan diimplementasikan oleh class yang juga mengimplementasikan interface lain. Pola ini cocok untuk hierarki peristiwa, state mesin, dan struktur domain yang terbatas. Pada chapter value class berikutnya, kita akan melihat class khusus untuk membungkus tipe primitif tanpa overhead alokasi memori.