Interface Bentuk yang kita bangun selama beberapa chapter terakhir terbuka bagi siapa saja: class baru mana pun boleh implements Bentuk tanpa izin. Keterbukaan tersebut kadang menguntungkan, tetapi kadang justru melemahkan — kode yang memproses semua variasi Bentuk tidak pernah tahu daftar lengkapnya, sehingga terpaksa memasang default yang menelan kasus tak terduga secara diam-diam. Java 17 (JEP 409) menghadirkan sealed classes: hierarki yang hanya boleh diperluas oleh tipe-tipe yang kita daftarkan sendiri lewat permits. Pada Chapter ini kita segel hierarki bangun datar, kombinasikan dengan record, lalu biarkan compiler menjaga kelengkapan pattern matching switch — tanpa default sama sekali.
Membatasi Hierarki dengan permits #
Kata kunci sealed pada class atau interface menyatakan bahwa perluasan ditutup. Daftar tipe yang berhak mewarisi dituliskan pada klausa permits — dan setiap tipe di daftar itu wajib hadir di berkas kompilasi yang sama atau modul yang sama. Sebagai gantinya, Java meminta setiap permitted subtype memilih salah satu dari tiga modifier:
final: cabang tertutup total — tidak bisa diwarisi lagi. Record otomatis final sehingga cocok menjadi ujung hierarki.sealed: cabang tertutup kembali denganpermitsmiliknya sendiri — sub-hierarki yang juga terkontrol.non-sealed: cabang dibuka kembali — siapa pun boleh mewarisi cabang ini, dan compiler tidak lagi bisa mengandalkan daftar lengkap untuk cabang tersebut.
Mencoba mewarisi tipe sealed tanpa menjadi bagian permits berujung compile error. Compiler javac mengeluarkan pesan class Segitiga is not allowed to extend sealed class: Bentuk (as it is not listed in its 'permits' clause) — penolakan yang terjadi di awal, bukan kejutan saat runtime. Lupa memasang ketiga modifier pada subclass juga ditolak dengan pesan sealed, non-sealed or final modifiers expected.
Hierarki Tersegel dan Switch Pattern Matching #
Kita segel kembali domain bangun datar: Lingkaran dan Persegi berupa record, Poligon berupa class non-sealed yang masih boleh diwarisi. Pemrosesan memakai pattern matching for switch (final di Java 21) dengan satu janji penting: tanpa default:
// Hierarki tersegel: hanya tiga tipe ini yang boleh mengimplementasikan Bentuk.
sealed interface Bentuk permits Lingkaran, Persegi, Poligon {
}
// Record otomatis final: menjadi ujung hierarki yang tertutup.
record Lingkaran(double radius) implements Bentuk {
}
record Persegi(double sisi) implements Bentuk {
}
// non-sealed: cabang ini dibuka kembali, boleh diwarisi class apa pun.
non-sealed class Poligon implements Bentuk {
private final int jumlahSisi;
public Poligon(int jumlahSisi) {
this.jumlahSisi = jumlahSisi;
}
public int jumlahSisi() {
return jumlahSisi;
}
}
// Karena Poligon non-sealed, pewarisan bebas diperbolehkan di cabang ini.
class SegitigaSiku extends Poligon {
public SegitigaSiku() {
super(3);
}
}
public class HierarkiTersegel {
static String deskripsi(Bentuk bentuk) {
// Switch pattern matching lengkap TANPA default:
// compiler memaksa semua permitted terliput.
return switch (bentuk) {
case Lingkaran(double radius) -> "lingkaran, radius " + radius;
case Persegi p -> "persegi, sisi " + p.sisi();
case Poligon p -> "poligon, " + p.jumlahSisi() + " sisi";
};
}
public static void main(String[] args) {
Bentuk[] daftar = {
new Lingkaran(7),
new Persegi(5),
new Poligon(6),
new SegitigaSiku() // subclass Poligon, tertangkap case Poligon
};
for (Bentuk b : daftar) {
System.out.println(deskripsi(b));
}
}
}Output yang Dihasilkan:
lingkaran, radius 7.0
persegi, sisi 5.0
poligon, 6 sisi
poligon, 3 sisiMari kita bedah potongan-potongan kuncinya:
sealed interface Bentuk permits Lingkaran, Persegi, Poligon: daftarpermitsadalah kontrak tertutup — di luar tiga nama tersebut, tidak ada jalan masuk ke hierarkiBentuk.record Lingkaran(double radius) implements Bentuk: record memenuhi kewajibanfinalsecara otomatis, sehinggaLingkarandanPersegimenjadi ujung hierarki tanpa modifier tambahan.non-sealed class Poligon: cabang ini sengaja dibuka.SegitigaSiku extends Poligonsah karena berada di cabang non-sealed, terbukti dari outputpoligon, 3 sisi.case Lingkaran(double radius): record pattern membongkar komponen record langsung di dalam case — variabelradiusterisi daril.radius()tanpa deklarasi terpisah.case Poligon p: type pattern menangkapPoligonbeserta seluruh subclass-nya (termasukSegitigaSiku). Kelengkapan switch tetap terjamin karena polaPoligonmencakup seluruh cabang non-sealed tersebut.- Tanpa
default:deskripsi(...)mengembalikanStringdari switch expression yang mencakup semua nilaiBentukyang mungkin. Jika ada nilai tak tercakup, program gagal kompilasi — bukan gagal diam-diam saat runtime.
Compiler Menjaga Kelengkapan Switch #
Manfaat terbesar sealed terlihat ketika kita lupa. Berkas berikut membuang case Persegi dari switch yang sama:
sealed interface Bentuk permits Lingkaran, Persegi {
}
record Lingkaran(double radius) implements Bentuk {
}
record Persegi(double sisi) implements Bentuk {
}
public class SwitchTidakLengkap {
static String deskripsi(Bentuk bentuk) {
// Kurang case Persegi: compiler menolak switch ini.
return switch (bentuk) {
case Lingkaran l -> "lingkaran, radius " + l.radius();
};
}
public static void main(String[] args) {
System.out.println(deskripsi(new Lingkaran(7)));
}
}Compiler javac menolak dengan pesan berikut:
error: the switch expression does not cover all possible input valuesPesan ini hanya muncul karena hierarkinya sealed — compiler mengetahui daftar lengkap semua kemungkinan nilai Bentuk. Pada hierarki terbuka, switch expression tanpa default selalu ditolak; default menjadi keharusan yang menelan kasus baru tanpa peringatan. Dengan sealed, begitu Anda menambah tipe baru ke permits, seluruh switch yang belum menanganinya langsung ditandai compiler — perubahan hierarki terlihat sejak kompilasi, bukan menjadi bug senyap di produksi.
Hierarki tersegel menutup seri fitur OOP modern yang kita jelajahi: dari abstract class, interface yang bisa berevolusi, hingga record dan sealed. Seluruh materi Section ini kini tinggal dirangkai menjadi prinsip kerja harian — bagaimana memilih antara komposisi dan pewarisan, seberapa ketat encapsulasi, dan bagaimana menjaga class tetap kecil. Chapter penutup Prinsip Desain OOP merangkumnya lewat satu studi kasus refactoring.
Referensi #
- Oracle: JEP 409 Sealed Classes
- Oracle: Java Language Specification — jls-8.1.1.2 sealed, non-sealed, final Classes
- Oracle: JEP 395 Records
- Oracle: Learning the Java Language
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
HierarkiTersegel.java
Unduh berkas// Hierarki tersegel: hanya tiga tipe ini yang boleh mengimplementasikan Bentuk.
sealed interface Bentuk permits Lingkaran, Persegi, Poligon {
}
// Record otomatis final: menjadi ujung hierarki yang tertutup.
record Lingkaran(double radius) implements Bentuk {
}
record Persegi(double sisi) implements Bentuk {
}
// non-sealed: cabang ini dibuka kembali, boleh diwarisi class apa pun.
non-sealed class Poligon implements Bentuk {
private final int jumlahSisi;
public Poligon(int jumlahSisi) {
this.jumlahSisi = jumlahSisi;
}
public int jumlahSisi() {
return jumlahSisi;
}
}
// Karena Poligon non-sealed, pewarisan bebas diperbolehkan di cabang ini.
class SegitigaSiku extends Poligon {
public SegitigaSiku() {
super(3);
}
}
public class HierarkiTersegel {
static String deskripsi(Bentuk bentuk) {
// Switch pattern matching lengkap TANPA default:
// compiler memaksa semua permitted terliput.
return switch (bentuk) {
case Lingkaran(double radius) -> "lingkaran, radius " + radius;
case Persegi p -> "persegi, sisi " + p.sisi();
case Poligon p -> "poligon, " + p.jumlahSisi() + " sisi";
};
}
public static void main(String[] args) {
Bentuk[] daftar = {
new Lingkaran(7),
new Persegi(5),
new Poligon(6),
new SegitigaSiku() // subclass Poligon, tertangkap case Poligon
};
for (Bentuk b : daftar) {
System.out.println(deskripsi(b));
}
}
}
SwitchTidakLengkap.java
Unduh berkassealed interface Bentuk permits Lingkaran, Persegi {
}
record Lingkaran(double radius) implements Bentuk {
}
record Persegi(double sisi) implements Bentuk {
}
public class SwitchTidakLengkap {
static String deskripsi(Bentuk bentuk) {
// Kurang case Persegi: compiler menolak switch ini.
return switch (bentuk) {
case Lingkaran l -> "lingkaran, radius " + l.radius();
};
}
public static void main(String[] args) {
System.out.println(deskripsi(new Lingkaran(7)));
}
}