Sealed Class dan Interface

Memodelkan hierarki tertutup dengan permits pada class dan interface sekaligus.

Daftar isi

Topik Sealed Class pada modul OOP II sudah mempraktikkan permits pada kelas beserta tiga penanda final, sealed, dan non-sealed. Topik ini menuntaskan sisi yang belum disentuh di sana, yaitu penyegelan pada interface, record sebagai penerapan yang alami, dan rantai permits yang bertingkat dari interface ke interface. Himpunan jenis yang tertutup semacam ini menjadi fondasi bagi pattern matching untuk switch pada topik selanjutnya, karena jaminan kelengkapannya berada di tangan kompiler. Pada akhir topik, hierarki yang menggabungkan kelas dan interface dapat dimodelkan tertutup sekaligus tanpa mencatat ulang daftar turunannya di dua tempat.

Sealed interface dan daftar penerapnya#

Interface yang disegel bekerja dengan aturan yang sama seperti kelas yang disegel. Daftar permits menyebut penerap langsung yang diizinkan, dan setiap penerap wajib memilih salah satu penanda. Perbedaannya ada pada kata kunci pemikirannya, karena penerap tidak lagi memakai extends, melainkan implements. Record menjadi penerap yang paling sering dipasangkan dengan sealed interface, karena deklarasi record sudah menyiratkan final sehingga cocok langsung tanpa penanda tambahan.

Program berikut memodelkan himpunan jenis acara sebagai sealed interface, dengan tiga record sebagai penerapnya.

java UjiJadwal.java
public class UjiJadwal {

    public static void main(String[] args) {
        Acara[] jadwal = {
            new Kuliah("Struktur Data", 90),
            new Tugas("Laporan praktikum", "Jumat"),
            new Ujian("R. 201", 60)
        };

        for (Acara a : jadwal) {
            System.out.println(ringkas(a));
        }
    }

    static String ringkas(Acara a) {
        if (a instanceof Kuliah k) {
            return "kuliah " + k.topik() + " (" + k.menit() + " menit)";
        } else if (a instanceof Tugas t) {
            return "tugas " + t.judul() + ", tenggat " + t.tenggat();
        } else if (a instanceof Ujian u) {
            return "ujian di " + u.ruangan() + " selama " + u.durasiMenit() + " menit";
        }
        return "acara tak dikenal";
    }
}

sealed interface Acara permits Kuliah, Tugas, Ujian {
}

record Kuliah(String topik, int menit) implements Acara {
}

record Tugas(String judul, String tenggat) implements Acara {
}

record Ujian(String ruangan, int durasiMenit) implements Acara {
}
text Keluaran
kuliah Struktur Data (90 menit)
tugas Laporan praktikum, tenggat Jumat
ujian di R. 201 selama 60 menit

Metode ringkas masih membawa cabang penolong return "acara tak dikenal" di ujungnya, dan itu memang wajar. Pemeriksaan instanceof tidak memiliki jaminan kelengkapan apa pun di mata kompiler, seberapa pun hierarkinya tertutup, sehingga cabang penolong itu tetap dibutuhkan. Daftar jenis yang tertutup baru menghasilkan manfaat penuh ketika pemeriksaannya dipindahkan ke switch, dan itulah yang dibahas pada topik pattern matching untuk switch.

Penolakan kompiler atas penerapan di luar daftar#

Gerbang permits pada interface ditutup saat kompilasi, sama seperti pada kelas. Kelas yang menyatakan implements tanpa masuk daftar ditolak, dan penerapan yang terdaftar tetapi ditulis polos juga ditolak. Pesan penolakannya dapat diuji langsung, dan hasilnya dituliskan pada dua potongan berikut.

java Contoh: penerap di luar daftar permits ditolak
// tidak-dijalankan
class Forum implements Acara {
}
// error: class is not allowed to extend sealed class: Acara
//        (as it is not listed in its 'permits' clause)

Pesan errornya memakai kata extend meskipun pelanggarannya terjadi pada implements, pilihan kata yang diberikan kompiler untuk kedua arah pewarisan. Yang penting dari pesan itu adalah lokasi penunjuknya, yaitu deklarasi penerap yang menyalahi daftar. Perbaikannya jelas: memasukkan kelas tersebut ke permits atau menghapus hubungannya.

java Contoh: penerap yang ditulis polos ditolak
// tidak-dijalankan
class Kuliah implements Acara {
}
// error: sealed, non-sealed or final modifiers expected

Penolakan kedua menyasar penanda, bukan daftar. Kuliah sudah masuk permits, tetapi tanpa penanda, kompiler tidak tahu nasib cabangnya, apakah ditutup, disegel lanjut, atau dibuka. Record bebas dari masalah ini karena finalitasnya sudah tertulis dalam deklarasinya, dan itulah alasan pasangan sealed interface dengan record terasa paling mulus.

Menyegel bertingkat dari interface ke interface#

permits tidak berhenti pada satu tingkat. Sealed interface boleh diperluas oleh sealed interface lain, dan setiap interface anak membawa permits miliknya sendiri. Induk hanya mencatat anak-anak langsungnya, sedangkan daun hierarki hanya dicatat oleh induk langsungnya, sehingga daftar pilihan tidak menumpuk di satu tempat. Program berikut memecah Sensor menjadi dua keluarga, analog dan digital, sebelum turun ke penerapnya.

java UjiSensor.java
public class UjiSensor {

    public static void main(String[] args) {
        Sensor[] sensor = {
            new SensorSuhu(28.5),
            new SensorGerak(true)
        };

        for (Sensor s : sensor) {
            System.out.println(baca(s));
        }
    }

    static String baca(Sensor s) {
        if (s instanceof SensorSuhu suhu) {
            return "suhu " + suhu.celsius() + " derajat";
        } else if (s instanceof SensorGerak gerak) {
            return "gerak terdeteksi: " + gerak.menyala();
        }
        return "sensor tak dikenal";
    }
}

sealed interface Sensor permits SensorAnalog, SensorDigital {
}

sealed interface SensorAnalog extends Sensor permits SensorSuhu {
}

sealed interface SensorDigital extends Sensor permits SensorGerak {
}

record SensorSuhu(double celsius) implements SensorAnalog {
}

record SensorGerak(boolean menyala) implements SensorDigital {
}
text Keluaran
suhu 28.5 derajat
gerak terdeteksi: true

Rantai penyegelannya terbaca dari atas ke bawah, yaitu Sensor menutup dua interface keluarganya, dan masing-masing keluarga menutup penerapnya. Keluarga analog berisi sensor suhu, sedangkan keluarga digital berisi sensor gerak, dan tidak ada satu pun jalan untuk menyelipkan penerap baru di luar daftar. Diagram berikut menuntaskan bentuk hierarki itu beserta jembatan antar tingkatnya.

sealed interface Sensorpermits: SensorAnalog, SensorDigitalsealed interface SensorAnalogpermits: SensorSuhusealed interface SensorDigitalpermits: SensorGerakrecord SensorSuhurecord SensorGerakextendsimplements
Rantai permits dari sealed interface ke sealed interface sampai penerapnya

Menimbang penyegelan pada kelas atau pada interface#

Pilihan antara sealed class dan sealed interface berputar pada apa yang dibagi oleh anggota hierarkinya. Bila turunan berbagi field dan perilaku konkret, misalnya kelas dasar yang menyimpan koordinat bersama, sealed class yang lebih tepat, sebagaimana dipraktikkan pada topik Sealed Class. Bila anggota hierarkinya murni jenis data yang berbeda-beda bentuknya, sealed interface dengan record penerap lebih ringkas, karena setiap jenis membawa komponennya sendiri tanpa field warisan yang menuntut konstruktor bertingkat.

Aturan keberadaan yang berlaku juga sama. Tanpa modul bernama, seluruh pihak pada permits wajib berada dalam package yang sama. Bila induk beserta penerapnya berada dalam satu berkas, permits bahkan boleh dilepas. Bagian yang memang dirancang terbuka bagi pihak luar tetap ditulis sebagai interface biasa, karena permits akan menghalangi perluasan yang justru diharapkan. Dengan begitu, penyegelan dipakai pada himpunan jenis yang tertutup secara domain, dan daerah perluasannya tetap terlihat jelas dari deklarasinya.

Kesalahan umum#

  • Mengira permits pada interface menerima penerap yang ditulis polos. Bentuk polos ditolak dengan pesan sealed, non-sealed or final modifiers expected. Pilih penandanya sesuai niat cabang, dan record tidak membutuhkannya karena sudah tersirat.
  • Mencatat seluruh penerap pada induk teratas. permits hanya menerima anak langsung, sehingga record pada tingkat bawah wajib dicatat oleh interface keluarganya, bukan oleh induknya.
  • Menunggu pemeriksaan instanceof menjadi ekshaustif karena hierarkinya tertutup. Pemeriksaan itu tetap membutuhkan cabang penolong, dan jaminan kelengkapannya baru berlaku pada switch yang dibahas pada topik pattern matching untuk switch.
  • Menyegel interface yang sebenarnya adalah titik perluasan, misalnya kontrak plugin. Daftar permits mengunci siapa saja yang boleh menerapkan, sehingga kontrak yang dibuka bagi pihak luar sebaiknya tetap interface biasa.

Ringkasan#

  • sealed berlaku pada interface dengan aturan yang sama seperti pada kelas, yaitu daftar permits untuk penerap langsung dan penanda final, sealed, atau non-sealed pada masing-masing penerap.
  • Record menjadi penerap alami karena finalitasnya tersirat, sehingga pasangan sealed interface dengan record memodelkan himpunan jenis tertutup dengan paling sedikit tulisan.
  • Penyegelan dapat berantai dari interface ke interface, dan setiap tingkat hanya mencatat anak langsungnya pada permits miliknya.
  • Pemeriksaan instanceof tetap membutuhkan cabang penolong seberapa pun hierarkinya tertutup, sedangkan jaminan kelengkapannya baru terpakai penuh oleh switch.

Referensi#