Method warisan kadang-kadang tidak lagi cocok dengan kebutuhan subclass. Buku menyusun teks deskripsi dari judul dan penulis, tetapi BukuFiksi ingin menambahkan genre — perilaku versi superclass perlu diganti dengan versi milik subclass. Proses penggantian inilah yang disebut method overriding: subclass mendeklarasikan ulang method warisan dengan tanda tangan yang sama persis, dan versi subclass-lah yang berjalan pada object subclass. Pada Chapter ini kita bedah mekanisme resolusinya saat runtime, anotasi @Override yang menjaganya dari salah ketik, serta tiga aturan batasnya: tipe kembalian yang boleh dipersempit secara kovarian, tingkat akses yang tidak boleh dipersempit, dan pengecualian yang tidak boleh diperluas.
Anatomi Sebuah Override #
Bahan percobaan kita tetap hierarki perpustakaan. Superclass Buku meng-override toString() — method warisan dari java.lang.Object — dan menyediakan method salinan() yang mengembalikan salinan object; subclass BukuFiksi menimpa keduanya:
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 String getPenulis() {
return penulis;
}
@Override
public String toString() {
return judul + " (" + penulis + ")";
}
public Buku salinan() {
return new Buku(judul, penulis);
}
}class BukuFiksi extends Buku {
private String genre;
BukuFiksi(String judul, String penulis, String genre) {
super(judul, penulis);
this.genre = genre;
}
@Override
public String toString() {
return super.toString() + " [fiksi]";
}
@Override
public BukuFiksi salinan() {
return new BukuFiksi(getJudul(), getPenulis(), genre);
}
}Tiga syarat membuat deklarasi di subclass menjadi override yang sah: nama method sama, daftar parameter sama persis, dan tipe kembalian sama — atau, sebagaimana kita bahas sebentar lagi, merupakan sub-tipe dari tipe kembalian superclass. Perhatikan pula badan toString() milik BukuFiksi yang memanggil super.toString(): subclass menyusun teks inti dari versi superclass lalu melampirkan [fiksi] — pola kerja sama yang menghindari duplikasi, persis seperti pada Chapter Keyword super.
Dynamic Dispatch di Balik Layar #
Mari kita uji dengan program yang mencetak object lewat tiga jalur berbeda. Method println milik System.out memanggil toString() secara implisit setiap kali menerima object:
public class AplikasiOverriding {
public static void main(String[] args) {
Buku kamus = new Buku("KBBI Edisi V", "Badan Pengembangan Bahasa");
BukuFiksi laskar = new BukuFiksi("Laskar Pelangi", "Andrea Hirata", "Roman");
System.out.println("cetak langsung : " + kamus);
System.out.println("cetak langsung : " + laskar);
Buku sebagaiBuku = laskar;
System.out.println("lewat variabel Buku: " + sebagaiBuku);
BukuFiksi duplikat = laskar.salinan();
System.out.println("salinan : " + duplikat);
}
}Output yang Dihasilkan:
cetak langsung : KBBI Edisi V (Badan Pengembangan Bahasa)
cetak langsung : Laskar Pelangi (Andrea Hirata) [fiksi]
lewat variabel Buku: Laskar Pelangi (Andrea Hirata) [fiksi]
salinan : Laskar Pelangi (Andrea Hirata) [fiksi]Baris ketiga output adalah inti dari seluruh chapter ini: teks yang tercetak tetap versi BukuFiksi meskipun object dicetak lewat variabel bertipe Buku. Mari kita bedah mekanisme di baliknya:
- Baris 6 (
System.out.println("cetak langsung : " + kamus);): operator+pada string memanggilkamus.toString()implisit. Karenakamusmenunjuk objectBuku, versi superclass-lah yang berjalan:KBBI Edisi V (Badan Pengembangan Bahasa). - Baris 7 (
System.out.println("cetak langsung : " + laskar);): objectBukuFiksimencetak versi subclass-nya —super.toString()menyusun teks inti, lalu[fiksi]dilampirkan. Override bekerja penuh. - Baris 9-10 (
Buku sebagaiBuku = laskar; ...): variabel bertipeBukukini menunjuk objectBukuFiksi. Compiler memeriksa pemanggilantoString()terhadap tipe variabel (Bukupun method tersebut), tetapi JVM yang memutuskan versi mana yang dijalankan saat runtime berdasarkan tipe object nyata — mekanisme ini bernama dynamic method dispatch. Tipe variabel hanya menentukan method apa yang boleh dipanggil; tipe object menentukan versi yang berjalan. - Baris 12 (
BukuFiksi duplikat = laskar.salinan();):salinan()versi superclass dideklarasikan mengembalikanBuku, tetapi versi subclass mengembalikanBukuFiksi— dan hasilnya langsung disimpan ke variabelBukuFiksitanpa cast. Inilah covariant return type yang kita bedah di bawah.
Covariant Return Type #
Sebelum Java 5, method yang menimpa wajib mengembalikan tipe yang persis identik dengan superclass. Aturan modernnya lebih longgar: tipe kembalian subclass boleh merupakan sub-tipe dari tipe kembalian superclass — aturan covariance. Buku.salinan() berjanji mengembalikan Buku; BukuFiksi.salinan() mempersempit janjinya menjadi BukuFiksi. Penyempitan ini aman karena setiap BukuFiksi adalah Buku (relasi IS-A dari Chapter Inheritance), sehingga pemanggil lama yang menganggap hasilnya Buku tidak pernah tertipu. Manfaat praktisnya terlihat pada baris 12 program kita: laskar.salinan() menghasilkan BukuFiksi yang siap dipakai tanpa pernyataan cast.
Aturan yang Menjaga Override Tetap Sehat #
Tiga pagar batas mengelilingi override. Pertama, tingkat akses tidak boleh menyempit: superclass memberi janji publik dengan public, dan subclass tidak boleh menariknya menjadi protected atau private. Compiler menolak dengan pesan verbatim deskripsi() in Anak cannot override deskripsi() in Induk disertai attempting to assign weaker access privileges; was public. Memperluas akses — dari protected ke public — justru diperbolehkan. Kedua, exception yang dideklarasikan tidak boleh lebih luas daripada superclass; pembahasan lengkapnya menunggu Section Exception Handling. Ketiga, method static tidak ikut ditimpa — method static milik class, bukan object, sehingga penyembunyiannya bermain dengan aturan berbeda (method hiding).
Sisa satu alat penting belum kita pasang: penanda @Override di atas method yang menimpa. Anotasi ini tidak mengubah perilaku program sama sekali — tugasnya adalah memaksa compiler memeriksa bahwa method di bawahnya benar-benar menimpa sesuatu. Kekuatannya tampak saat salah ketik terjadi:
class BukuFiksi extends Buku {
@Override
public String toStrng() { // salah ketik: satu huruf hilang dari toString
return "buku fiksi";
}
}Compiler javac menolak potongan tersebut dengan pesan verbatim method does not override or implement a method from a supertype. Tanpa anotasi @Override, ceritanya berbeda dan lebih mencemaskan: toStrng() diterima sebagai method baru yang sah, program dikompilasi sukses, dan override yang kita kira terjadi tidak pernah terjadi — toString() warisan Object tetap dipakai, biasanya dengan hasil cetak yang membingungkan. Salah ketik satu huruf berubah menjadi kesalahan senyap yang sulit dicari.
Dengan overriding, hierarki class memperoleh kemampuan yang menjadikan OOP sungguh-sungguh lentur: kode yang ditulis melawan superclass dapat berperilaku berbeda pada setiap subclass tanpa if-else bersarang. Kapabilitas ini akan kita angkat ke level berikutnya pada Chapter Polymorphism — memproses campuran object Buku, BukuFiksi, dan BukuReferensi dalam satu array, lalu menyelidiki downcast beserta risikonya.
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.
AplikasiOverriding.java
Unduh berkaspublic class AplikasiOverriding {
public static void main(String[] args) {
Buku kamus = new Buku("KBBI Edisi V", "Badan Pengembangan Bahasa");
BukuFiksi laskar = new BukuFiksi("Laskar Pelangi", "Andrea Hirata", "Roman");
System.out.println("cetak langsung : " + kamus);
System.out.println("cetak langsung : " + laskar);
Buku sebagaiBuku = laskar;
System.out.println("lewat variabel Buku: " + sebagaiBuku);
BukuFiksi duplikat = laskar.salinan();
System.out.println("salinan : " + duplikat);
}
}
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 String getPenulis() {
return penulis;
}
@Override
public String toString() {
return judul + " (" + penulis + ")";
}
public Buku salinan() {
return new Buku(judul, 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 String toString() {
return super.toString() + " [fiksi]";
}
@Override
public BukuFiksi salinan() {
return new BukuFiksi(getJudul(), getPenulis(), genre);
}
}