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.
// 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:
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 databaseMari kita bedah kode di atas:
sealed interface Peristiwa { val waktu: String }: sealed interface dengan satu property abstrak. Semua class yang mengimplementasikanPeristiwaharus berada di file yang sama dan menyediakan propertywaktu.data class KlikTombol(val tombol: String, override val waktu: String) : Peristiwa: subtypePeristiwaberupadata class. Propertywaktudi-override dari interface —override valwajib karena property tersebut bersifat abstrak di interface.object AplikasiDimulai : Peristiwa { override val waktu: String = "00:00" }: subtype berupaobject. Object singleton juga dapat mengimplementasikan sealed interface. Karena object tidak punya constructor, propertywaktudiinisialisasi langsung dengan literal.sealed interface BentukGeometri { fun luas(): Double }; sealed class BangunDatar : BentukGeometri { ... }: sealed interfaceBentukGeometridiimplementasikan oleh sealed classBangunDatar. Pola bertingkat ini membuat hierarki dua tingkat —BentukGeometrisebagai kontrak umum,BangunDatarsebagai grup subtype dengan subhierarki sendiri. FunctionringkasBentukcukup memproses tiga subtype langsungBangunDatar— kompilator tetap menganggapwhenexhaustive karena tidak ada subtypeBentukGeometrilain di luarBangunDatar.sealed interface PeristiwaUI : Peristiwa { val komponen: String }: sealed interface yang mewarisi sealed interface lain. SubtypePeristiwaUIotomatis menjadi subtypePeristiwajuga —KlikKomponendanFokusKomponendapat diperlakukan sebagaiPeristiwadan muncul dalam daftarList<Peristiwa>.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. InstanceCatatanPeristiwadapat diperlakukan sebagaiPeristiwa(mengakseswaktu) maupunPersisten(memanggilsimpan()).when (p) { is KlikTombol -> ...; ...; is CatatanPeristiwa -> ... }: functionringkasmenerimaPeristiwa(interface sealed).whenmenangani semua subtype —KlikTombol,GulirLayar,AplikasiDimulai,KlikKomponen,FokusKomponen, danCatatanPeristiwa. Tidak perluelsekarena daftar lengkap.catatan is Peristiwa,catatan is Persisten: dua pemeriksaan tipe pada instance yang sama. KarenaCatatanPeristiwamengimplementasikan kedua interface, kedua pemeriksaan menghasilkantrue.
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 #
- Sealed classes — bagian sealed interface sebagai hierarki tertutup
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.