Sampai titik ini kita memiliki hierarki Buku → BukuFiksi / BukuReferensi dengan method yang sama namun perilaku berbeda di tiap class. Pilar keempat OOP, polymorphism (banyak bentuk), adalah momen semua keping itu saling mengunci: satu variabel bertipe Buku boleh menunjuk object Buku, BukuFiksi, atau BukuReferensi, dan satu panggilan deskripsi() menghasilkan tiga kelakuan berbeda — versi mana yang berjalan ditentukan saat runtime. Kode pengolah rak tidak perlu tahu jenis tiap buku; cukup berbicara pada tipe Buku dan biarkan JVM bekerja. Pada Chapter ini kita buktikan perilaku tersebut lewat array heterogen, lalu menutupnya dengan topik yang menuntut kehati-hatian: downcast beserta ClassCastException yang mengintainya.
Variabel Parent Menunjuk Object Child #
Aturan dasarnya satu kalimat: variabel boleh menunjuk object yang bertipe sub-tipe dari tipe variabelnya. Pernyataan Buku buku = new BukuFiksi(...) sah karena setiap BukuFiksi adalah Buku — relasi IS-A yang kita bangun lewat extends pada Chapter Inheritance. Compiler dan JVM kemudian membagi tugas dengan tegas. Compiler memeriksa setiap pemanggilan method terhadap tipe variabel: deskripsi() dan durasiPinjamHari() boleh dipanggil karena keduanya dideklarasikan pada Buku. JVM pada saat runtime memilih versi method berdasarkan tipe object nyata yang ditunjuk — mekanisme dynamic method dispatch yang sudah kita saksikan bekerja pada Chapter Method Overriding.
Konsekuensi praktisnya luas. Kode yang ditulis melawan superclass otomatis menerima object subclass apa pun — hari ini maupun subclass yang baru ditulis tahun depan — tanpa satu baris perubahan. Sifat inilah yang menjadikan polymorphism alat pengendali kompleksitas terkuat dalam OOP.
Array Heterogen dan Pemanggilan Runtime #
Perpustakaan kita akan menampung tiga jenis buku dalam satu array bertipe Buku[]. Setiap elemen array boleh berisi object dari subclass mana pun karena seluruhnya memenuhi janji tipe Buku:
class Buku {
private String judul;
private String penulis;
Buku(String judul, String penulis) {
this.judul = judul;
this.penulis = penulis;
}
public String getJudul() {
return judul;
}
public int durasiPinjamHari() {
return 14;
}
public String deskripsi() {
return "Buku umum: " + judul + " oleh " + penulis;
}
}class BukuFiksi extends Buku {
private String genre;
BukuFiksi(String judul, String penulis, String genre) {
super(judul, penulis);
this.genre = genre;
}
@Override
public int durasiPinjamHari() {
return 21;
}
@Override
public String deskripsi() {
return "Fiksi " + genre + ": " + getJudul();
}
}class BukuReferensi extends Buku {
private int edisi;
BukuReferensi(String judul, String penulis, int edisi) {
super(judul, penulis);
this.edisi = edisi;
}
@Override
public int durasiPinjamHari() {
return 3;
}
@Override
public String deskripsi() {
return "Referensi edisi ke-" + edisi + ": " + getJudul();
}
public String aturanRuangBaca() {
return getJudul() + " hanya boleh dibaca di ruang baca (edisi ke-"
+ edisi + ")";
}
}Perhatikan bahwa hanya BukuReferensi yang memiliki method aturanRuangBaca() — detail ini akan menjadi bahan percobaan downcast kita. Program pemanggilnya memproses seluruh rak dengan satu loop:
public class AplikasiPolymorphism {
public static void main(String[] args) {
Buku[] rak = {
new Buku("Panduan Anggota Perpustakaan", "Siti Rahmawati"),
new BukuFiksi("Laskar Pelangi", "Andrea Hirata", "Roman"),
new BukuReferensi("KBBI Edisi V", "Badan Pengembangan Bahasa", 5)
};
System.out.println("--- kunjungan rak (dynamic dispatch) ---");
for (Buku buku : rak) {
System.out.println(buku.deskripsi() + " | pinjam "
+ buku.durasiPinjamHari() + " hari");
}
System.out.println("--- downcast dengan pemeriksaan ---");
for (Buku buku : rak) {
if (buku instanceof BukuReferensi) {
BukuReferensi ref = (BukuReferensi) buku;
System.out.println(ref.aturanRuangBaca());
} else {
System.out.println(buku.getJudul() + " bukan buku referensi");
}
}
System.out.println("--- downcast tanpa pemeriksaan ---");
try {
BukuReferensi ref = (BukuReferensi) rak[1];
System.out.println(ref.aturanRuangBaca());
} catch (ClassCastException e) {
System.out.println("Gagal: " + e.getClass().getSimpleName());
}
}
}Output yang Dihasilkan:
--- kunjungan rak (dynamic dispatch) ---
Buku umum: Panduan Anggota Perpustakaan oleh Siti Rahmawati | pinjam 14 hari
Fiksi Roman: Laskar Pelangi | pinjam 21 hari
Referensi edisi ke-5: KBBI Edisi V | pinjam 3 hari
--- downcast dengan pemeriksaan ---
Panduan Anggota Perpustakaan bukan buku referensi
Laskar Pelangi bukan buku referensi
KBBI Edisi V hanya boleh dibaca di ruang baca (edisi ke-5)
--- downcast tanpa pemeriksaan ---
Gagal: ClassCastExceptionBagian pertama output menampilkan tiga baris dengan tiga kelakuan berbeda dari method yang sama. Mari kita bedah:
- Baris 5-9 (
Buku[] rak = { ... }): satu array tipeBukuberisi tiga object bertipe berbeda — bentuk paling sederhana dari struktur data heterogen. Collection sepertiArrayList<Buku>berperilaku serupa dan akan kita bahas pada Section Collections Framework. - Baris 12-15 (
for (Buku buku : rak)): loop membaca setiap elemen sebagaiBukutanpa peduli jenis aslinya. Pemanggilanbuku.deskripsi()danbuku.durasiPinjamHari()diizinkan compiler karena kedua method ada diBuku; JVM kemudian menjalankan versi sesuai object nyata — 14 hari untuk buku umum, 21 untuk fiksi, 3 untuk referensi. Tidak ada satu punifyang memeriksa jenis buku. - Baris 18-26 (
if (buku instanceof BukuReferensi)): operatorinstanceofmenguji tipe object nyata sebelum downcast dilakukan. Hasiltruehanya pada elemen ketiga, sehingga cast(BukuReferensi) bukuaman dan methodaturanRuangBaca()— yang hanya dimilikiBukuReferensi— akhirnya bisa dipanggil.
Downcast dan ClassCastException #
Method aturanRuangBaca() tidak bisa dipanggil lewat variabel Buku — compiler menolaknya karena method tersebut tidak ada pada Buku. Jalan satu-satunya adalah downcast: mengubah referensi ke tipe yang lebih spesifik dengan penulisan cast (BukuReferensi). Cast semacam ini tidak mengubah object sama sekali; cast hanya memberi tahu compiler bahwa object di balik referensi bertipe BukuReferensi. Bila keyakinan itu salah, JVM melempar ClassCastException tepat saat cast dievaluasi.
Bagian ketiga program kita memperagakan skenario tanpa pengaman: rak[1] berisi object BukuFiksi, lalu dipaksa menjadi BukuReferensi — dua subclass yang tidak berhubungan pewarisan satu sama lain. JVM menolak dan melempar ClassCastException; blok catch menangkapnya sehingga program tetap berlanjut. Pesan lengkap exception tersebut berpola class BukuFiksi cannot be cast to class BukuReferensi — contoh kita sengaja hanya mencetak nama exceptionnya (ClassCastException) agar output tetap ringkas, karena sisanya berisi keterangan modul dan loader yang panjang dan berubah antar lingkungan eksekusi.
Sejak Java 16, pengujian dan pengecoran dapat digabung dalam satu sintaks yang disebut pattern matching for instanceof:
if (buku instanceof BukuReferensi ref) {
System.out.println(ref.aturanRuangBaca()); // ref sudah siap pakai
}Compiler mendeklarasikan ref secara otomatis dengan tipe yang sudah dipersempit, dan blok if hanya dijalankan bila pemeriksaan berhasil — pekerjaan dua pernyataan selesai dalam satu baris.
Kapan Polymorphism Membayar Diri #
Nilai polymorphism paling terasa ketika bentuk-bentuk baru datang belakangan. Tambahkan class BukuLangka dengan durasiPinjamHari() tujuh hari, dan loop kunjungan rak kita bekerja untuk jenis baru itu tanpa satu baris perubahan — inilah prinsip open for extension, closed for modification. Tanpa polymorphism, pilihan yang tersisa adalah rantai if-else yang memeriksa jenis tiap object dan harus disunting setiap kali jenis baru ditambahkan; dengan polymorphism, setiap class membawa perilakunya sendiri dan kode pemanggil tetap generis.
Perjalanan 13 Chapter kita menutup satu lingkaran penuh: dari cetak biru class, object dengan state terlindungi, hingga hierarki yang hidup dan bergerak lewat dynamic dispatch. Namun hierarki kita masih menyisakan satu keganjilan: Buku versi sekarang bisa dijadikan langsung, padahal dalam domain nyata “buku umum” hampir selalu berarti buku fiksi, referensi, atau jenis lain yang lebih spesifik. Java menyediakan class yang memang tidak boleh dijadikan object — hanya boleh diwarisi — bernama abstract class, pembuka babak berikutnya dari petualangan OOP kita.
Referensi #
- Oracle: dev.java — Object-Oriented Programming
- Java Language Specification — Chapter 8: Classes
- API java.base — java.lang.Object
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
AplikasiPolymorphism.java
Unduh berkaspublic class AplikasiPolymorphism {
public static void main(String[] args) {
Buku[] rak = {
new Buku("Panduan Anggota Perpustakaan", "Siti Rahmawati"),
new BukuFiksi("Laskar Pelangi", "Andrea Hirata", "Roman"),
new BukuReferensi("KBBI Edisi V", "Badan Pengembangan Bahasa", 5)
};
System.out.println("--- kunjungan rak (dynamic dispatch) ---");
for (Buku buku : rak) {
System.out.println(buku.deskripsi() + " | pinjam "
+ buku.durasiPinjamHari() + " hari");
}
System.out.println("--- downcast dengan pemeriksaan ---");
for (Buku buku : rak) {
if (buku instanceof BukuReferensi) {
BukuReferensi ref = (BukuReferensi) buku;
System.out.println(ref.aturanRuangBaca());
} else {
System.out.println(buku.getJudul() + " bukan buku referensi");
}
}
System.out.println("--- downcast tanpa pemeriksaan ---");
try {
BukuReferensi ref = (BukuReferensi) rak[1];
System.out.println(ref.aturanRuangBaca());
} catch (ClassCastException e) {
System.out.println("Gagal: " + e.getClass().getSimpleName());
}
}
}
Buku.java
Unduh berkasclass Buku {
private String judul;
private String penulis;
Buku(String judul, String penulis) {
this.judul = judul;
this.penulis = penulis;
}
public String getJudul() {
return judul;
}
public int durasiPinjamHari() {
return 14;
}
public String deskripsi() {
return "Buku umum: " + judul + " oleh " + penulis;
}
}
BukuFiksi.java
Unduh berkasclass BukuFiksi extends Buku {
private String genre;
BukuFiksi(String judul, String penulis, String genre) {
super(judul, penulis);
this.genre = genre;
}
@Override
public int durasiPinjamHari() {
return 21;
}
@Override
public String deskripsi() {
return "Fiksi " + genre + ": " + getJudul();
}
}
BukuReferensi.java
Unduh berkasclass BukuReferensi extends Buku {
private int edisi;
BukuReferensi(String judul, String penulis, int edisi) {
super(judul, penulis);
this.edisi = edisi;
}
@Override
public int durasiPinjamHari() {
return 3;
}
@Override
public String deskripsi() {
return "Referensi edisi ke-" + edisi + ": " + getJudul();
}
public String aturanRuangBaca() {
return getJudul() + " hanya boleh dibaca di ruang baca (edisi ke-"
+ edisi + ")";
}
}