Overriding adalah mengganti implementasi metode induk di kelas turunan dengan tanda tangan (signature) yang sama, sehingga panggilan yang sama dijawab dengan perilaku turunannya sendiri. Topik ini mempraktikkan aturan overriding beserta anotasi @Override, tipe kembalian kovarian (covariant return type), dan perbedaan hiding pada metode static dari overriding yang sesungguhnya. Ketiganya menentukan metode mana yang benar-benar berjalan ketika program dieksekusi.
Syarat overriding dan pola dasarnya#
Metode turunan mengoverride metode induk bila namanya sama, daftar parameternya sama, dan tipe kembaliannya sama atau lebih spesifik. Tingkat aksesnya tidak boleh menyempit, misalnya dari public menjadi private, dan exception yang diperiksa (checked exception) tidak boleh diperlebar. Bila semua syarat terpenuhi, setiap pemanggilan lewat objek turunan menjalankan versi turunan, meskipun pemanggilnya menulis kode terhadap tipe induk.
public class DemoOverriding {
public static void main(String[] args) {
Penilaian dasar = new Penilaian(70, 90);
PenilaianPraktik praktik = new PenilaianPraktik(70, 90, 80);
System.out.println("Dasar : " + dasar.rincian());
System.out.println("Praktik : " + praktik.rincian());
}
}
class Penilaian {
double tulis;
double unggah;
Penilaian(double tulis, double unggah) {
this.tulis = tulis;
this.unggah = unggah;
}
double nilaiAkhir() {
return tulis * 0.5 + unggah * 0.5;
}
String rincian() {
return "nilai akhir " + nilaiAkhir();
}
}
class PenilaianPraktik extends Penilaian {
double demonstrasi;
PenilaianPraktik(double tulis, double unggah, double demonstrasi) {
super(tulis, unggah);
this.demonstrasi = demonstrasi;
}
@Override
double nilaiAkhir() {
return tulis * 0.5 + unggah * 0.25 + demonstrasi * 0.25;
}
}Dasar : nilai akhir 80.0
Praktik : nilai akhir 77.5Kelas PenilaianPraktik tidak menyentuh rincian() sama sekali, tetapi hasilnya berbeda karena rincian() di induk memanggil nilaiAkhir(), dan yang berjalan adalah versi PenilaianPraktik. Pola ini terasa di banyak kode nyata, yaitu metode umum di induk memanggil langkah yang dapat diganti oleh turunan. Metode induk yang tidak boleh diganti ditandai final, dan kompiler akan menolak overriding atasnya.
@Override sebagai penjaga tanda tangan#
Anotasi @Override bersifat opsional, tetapi memasangnya pada setiap metode yang memang dimaksudkan mengganti induk adalah kebiasaan yang murah dan menolong. Anotasi itu meminta kompiler memeriksa bahwa tanda tangan metodenya benar-benar cocok dengan metode induk. Bila cocok, kompiler diam. Bila tidak, misalnya karena salah ketik nama atau parameter yang berbeda, kompiler menolak berkasnya dengan pesan method does not override or implement a method from a supertype.
// tidak-dijalankan
@Override
double nilaiAkhir(double bobot) { // parameter berbeda, bukan overriding
return 0;
}Tanpa @Override, potongan di atas tetap dikompilasi, tetapi maknanya berubah menjadi overloading, yaitu metode baru bernama sama dengan daftar parameter berbeda. Metode induk tidak pernah terganti, dan kesalahan itu baru terasa ketika perilaku lama terus muncul di tempat yang seharusnya sudah berganti. Dengan @Override, kesalahan yang sama berhenti di layar kompilasi sebelum sempat berjalan.
Tipe kembalian kovarian#
Sejak Java 5, tipe kembalian metode overriding boleh menjadi turunan dari tipe kembalian metode induknya, aturan yang disebut kovariansi. Metode induk boleh menjanjikan hasil bertipe umum, sedangkan turunan menajamkan janjinya ke tipe yang lebih spesifik tanpa mengubah tanda tangannya. Aturan ini hanya berlaku untuk tipe referensi, karena tipe primitif tidak punya hierarki turunan sehingga tipe kembaliannya wajib sama persis.
public class UjiKovariansi {
public static void main(String[] args) {
Rencana dasar = new Rencana(7);
RencanaMingguan mingguan = new RencanaMingguan(7);
RencanaMingguan hasil = mingguan.perbarui(3);
System.out.println(dasar.deskripsi());
System.out.println(hasil.deskripsi());
}
}
class Rencana {
int hari;
Rencana(int hari) {
this.hari = hari;
}
String deskripsi() {
return "rencana selama " + hari + " hari";
}
Rencana perbarui(int tambahan) {
return new Rencana(hari + tambahan);
}
}
class RencanaMingguan extends Rencana {
RencanaMingguan(int hari) {
super(hari);
}
@Override
String deskripsi() {
return "rencana mingguan selama " + hari + " hari";
}
@Override
RencanaMingguan perbarui(int tambahan) {
return new RencanaMingguan(hari + tambahan);
}
}rencana selama 7 hari
rencana mingguan selama 10 hariInduk Rencana menjanjikan hasil bertipe Rencana, sedangkan RencanaMingguan menajamkannya menjadi RencanaMingguan. Berkat kovariansi, hasil mingguan.perbarui(3) dapat disimpan langsung ke variabel RencanaMingguan tanpa casting, dan anggota khas turunan tetap terjangkau dari pemanggil. Bila tipe kembaliannya dipaksa sama persis, pemanggil yang membutuhkan tipe spesifik harus menulis cast sendiri setiap kali.
Hiding pada metode static#
Metode static tidak mengikuti aturan overriding. Pemilihannya dilakukan kompiler berdasarkan tipe referensi saat kompilasi, bukan tipe objek saat program berjalan. Mendefinisikan metode static bernama sama di turunan karenanya disebut hiding, yaitu menyembunyikan versi induk dari sudut pandang nama turunan, bukan menggantinya secara perilaku.
public class DemoHiding {
public static void main(String[] args) {
Pengaturan.cetakSumber();
PengaturanLanjut.cetakSumber();
}
}
class Pengaturan {
static String sumber() {
return "berkas konfigurasi utama";
}
static void cetakSumber() {
System.out.println("Sumber: " + sumber());
}
}
class PengaturanLanjut extends Pengaturan {
static String sumber() {
return "berkas konfigurasi lokal";
}
}Sumber: berkas konfigurasi utama
Sumber: berkas konfigurasi utamaPemanggilan kedua tetap mencetak versi induk, dan inilah bukti bedanya dengan overriding. Metode cetakSumber() diwarisi PengaturanLanjut tanpa ditulis ulang, sehingga isi badannya tetap memanggil Pengaturan.sumber() yang terikat saat kompilasi. Versi sumber() milik PengaturanLanjut hanya berlaku ketika dipanggil lewat nama PengaturanLanjut atau referensi bertipe itu. Perilaku yang mengikuti objek, bukan tipe referensinya, sebaiknya dinyatakan sebagai metode instance dan dioverride secara sah.
| Aspek | Overriding metode instance | Hiding metode static |
|---|---|---|
| Metode dipilih berdasarkan | tipe objek saat program berjalan | tipe referensi saat kompilasi |
| Panggilan lewat referensi induk | versi turunan berjalan | versi induk berjalan |
| Wajar dipakai untuk | perilaku milik objek | utilitas kelas yang tidak memakai objek |
Kesalahan umum#
- Mengubah daftar parameter ketika maksudnya mengganti metode. Hasilnya metode baru (overloading) yang hidup di samping versi induk, sementara pemanggil lama tetap memakai versi induk. Pasang
@Overrideagar selisih tanda tangan tertangkap kompiler. - Mengira metode
staticikut dioverride. Yang terjadi adalah hiding, sehingga panggilan lewat referensi induk tetap menjalankan versi induk. Bila perilakunya milik objek, jadikan metode instance. - Menyempitkan tingkat akses saat mengoverride. Kompiler menolak dengan pesan
attempting to assign weaker access privileges; was public. Pertahankan tingkat akses yang sama atau lebarkan bila memang diperlukan.
Ringkasan#
- Overriding mengganti implementasi metode induk dengan tanda tangan sama, tingkat akses sama atau lebih luas, dan tipe kembalian sama atau kovarian.
- Anotasi
@Overridememinta kompiler memeriksa kecocokan tanda tangan, sehingga salah ketik berhenti sebelum program berjalan. - Metode
statictidak dioverride melainkan dihiding, dan pemilihannya mengikuti tipe referensi pada saat kompilasi.
Referensi#
- The Java Tutorials: Overriding and Hiding Methods — Oracle
— Panduan resmi syarat overriding serta perilaku hiding pada metode
static. - JLS SE 21 §8.4.8: Inheritance, Overriding, and Hiding — Oracle — Spesifikasi lengkap aturan overriding, kovariansi, dan hiding.
- JLS SE 21 §8.4.5: Method Signature — Oracle — Definisi tanda tangan metode yang menjadi dasar pembeda overriding dan overloading.