Eksekusi program Java memerlukan dua langkah: kompilasi dan eksekusi (interpretasi). Pada saat yang sama, Java adalah bahasa pemrograman berorientasi objek. Ketika subkelas dan kelas induk memiliki metode yang sama, dan subkelas tersebut mengganti metode kelas induk, ketika program memanggil metode tersebut pada saat runtime, haruskah ia memanggil metode kelas induk atau metode subkelas yang diganti? seharusnya menjadi pertanyaan saat pertama kali kita belajar Java masalah yang ditemui. Disini pertama kita akan menentukan metode mana yang akan dipanggil atau pengoperasian variabel disebut mengikat.
Ada dua metode pengikatan di Java, yang pertama adalah pengikatan statis, disebut juga pengikatan awal. Yang lainnya adalah pengikatan dinamis, juga dikenal sebagai pengikatan akhir.
Perbandingan perbedaan
1. Pengikatan statis terjadi pada waktu kompilasi, dan pengikatan dinamis terjadi pada waktu proses.
2. Gunakan variabel atau metode yang dimodifikasi dengan private, static atau final, dan gunakan static binding. Metode virtual (metode yang dapat ditimpa oleh subkelas) akan terikat secara dinamis berdasarkan objek runtime.
3. Pengikatan statis diselesaikan menggunakan informasi kelas, sedangkan pengikatan dinamis perlu diselesaikan menggunakan informasi objek.
4. Metode kelebihan beban diselesaikan menggunakan pengikatan statis, sedangkan metode overriding diselesaikan menggunakan pengikatan dinamis.
Contoh metode kelebihan beban
Berikut adalah contoh metode yang kelebihan beban.
Copy kode kodenya sebagai berikut:
kelas publik TesUtama {
public static void main(String[] args) {
String str = String baru();
Penelepon penelepon = Penelepon baru();
pemanggil.panggilan(str);
}
pemanggil kelas statis {
panggilan kekosongan publik(Obj objek) {
System.out.println("sebuah instance Objek di Caller");
}
panggilan kekosongan publik(String str) {
System.out.println("sebuah instance String di dalam Caller");
}
}
}
Hasil eksekusinya adalah
Copy kode kodenya sebagai berikut:
22:19 $javaTestUtama
sebuah instance String di Caller
Dalam kode di atas, ada dua implementasi metode panggilan yang kelebihan beban. Yang satu menerima objek bertipe Object sebagai parameter, dan yang lainnya menerima objek bertipe String sebagai parameter. str adalah objek String, dan semua metode panggilan yang menerima parameter tipe String akan dipanggil. Pengikatan di sini adalah pengikatan statis berdasarkan tipe parameter pada waktu kompilasi.
memeriksa
Melihat tampilannya saja tidak dapat membuktikan bahwa pengikatan statis dilakukan. Anda dapat memverifikasinya dengan menggunakan javap untuk mengkompilasinya.
Copy kode kodenya sebagai berikut:
22:19 $ javap -c TesUtama
Dikompilasi dari "TestMain.java"
kelas publik TesUtama {
TesUtamaUtama();
Kode:
0: memuat_0
1: invokespecial #1 // Metode java/lang/Object."<init>":()V
4: kembali
public static void main(java.lang.String[]);
Kode:
0: baru #2 // kelas java/lang/String
3: bodoh
4: invokespecial #3 // Metode java/lang/String."<init>":()V
7: toko_1
8: #4 baru // kelas TestMain$Caller
11: bodoh
12: invokespecial #5 // Metode TestMain$Caller."<init>":()V
15: toko_2
16: memuat_2
17: memuat_1
18: invokevirtual #6 // Metode TestMain$Caller.call:(Ljava/lang/String;)V
21: kembali
}
Saya melihat baris ini 18: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V memang terikat secara statis, yang mengonfirmasi bahwa metode pemanggil yang menerima objek String sebagai parameter dipanggil.
Contoh menimpa suatu metode
Copy kode kodenya sebagai berikut:
kelas publik TesUtama {
public static void main(String[] args) {
String str = String baru();
Penelepon pemanggil = SubCaller baru();
pemanggil.panggilan(str);
}
pemanggil kelas statis {
panggilan kekosongan publik(String str) {
System.out.println("sebuah instance String di Caller");
}
}
SubCaller kelas statis memperluas Penelepon {
@Mengesampingkan
panggilan kekosongan publik(String str) {
System.out.println("sebuah instance String di SubCaller");
}
}
}
Hasil eksekusinya adalah
Copy kode kodenya sebagai berikut:
22:27 $javaTestUtama
sebuah instance String di SubCaller
Pada kode di atas, terdapat implementasi metode panggilan di Caller. SubCaller mewarisi Caller dan menulis ulang implementasi metode panggilan. Kita mendeklarasikan variabel callerSub bertipe Caller, namun variabel ini menunjuk ke objek SubCaller. Berdasarkan hasil, terlihat bahwa ia memanggil implementasi metode panggilan SubCaller, bukan metode panggilan Caller. Alasan untuk hasil ini adalah pengikatan dinamis terjadi saat runtime, dan selama proses pengikatan, perlu ditentukan versi implementasi metode panggilan mana yang akan dipanggil.
memeriksa
Pengikatan dinamis tidak dapat diverifikasi secara langsung menggunakan javap, dan jika terbukti pengikatan statis tidak dilakukan, berarti pengikatan dinamis dilakukan.
Copy kode kodenya sebagai berikut:
22:27 $ javap -c TesUtama
Dikompilasi dari "TestMain.java"
kelas publik TesUtama {
TesUtamaUtama();
Kode:
0: memuat_0
1: invokespecial #1 // Metode java/lang/Object."<init>":()V
4: kembali
public static void main(java.lang.String[]);
Kode:
0: baru #2 // kelas java/lang/String
3: bodoh
4: invokespecial #3 // Metode java/lang/String."<init>":()V
7: toko_1
8: #4 baru // kelas TestMain$SubCaller
11: bodoh
12: invokespecial #5 // Metode TestMain$SubCaller."<init>":()V
15: toko_2
16: memuat_2
17: memuat_1
18: invokevirtual #6 // Metode TestMain$Caller.call:(Ljava/lang/String;)V
21: kembali
}
Seperti hasil di atas, 18: invokevirtual #6 // Metode TestMain$Caller.call:(Ljava/lang/String;)V Ini adalah TestMain$Caller.call dan bukan TestMain$SubCaller.call, karena subrutin pemanggilan tidak dapat ditentukan pada waktu kompilasi Kelas tersebut masih merupakan implementasi dari kelas induk, sehingga hanya dapat ditangani dengan pengikatan dinamis pada saat runtime.
Saat memuat ulang bertemu dengan menulis ulang
Contoh berikut agak tidak normal. Ada dua kelebihan metode panggilan di kelas Caller. Yang lebih rumit adalah SubCaller mengintegrasikan Caller dan mengesampingkan kedua metode ini. Faktanya, situasi ini merupakan situasi gabungan dari dua situasi di atas.
Kode berikut pertama-tama akan melakukan pengikatan statis untuk menentukan metode panggilan yang parameternya adalah objek String, dan kemudian melakukan pengikatan dinamis saat runtime untuk menentukan apakah akan menjalankan implementasi panggilan subkelas atau kelas induk.
Copy kode kodenya sebagai berikut:
kelas publik TesUtama {
public static void main(String[] args) {
String str = String baru();
Penelepon callerSub = SubCaller baru();
callerSub.call(str);
}
pemanggil kelas statis {
panggilan kekosongan publik(Obj objek) {
System.out.println("sebuah instance Objek di Caller");
}
panggilan kekosongan publik(String str) {
System.out.println("sebuah instance String di dalam Caller");
}
}
SubCaller kelas statis memperluas Penelepon {
@Mengesampingkan
panggilan kekosongan publik(Obj objek) {
System.out.println("sebuah instance Objek di SubCaller");
}
@Mengesampingkan
panggilan kekosongan publik(String str) {
System.out.println("sebuah instance String di dalam SubCaller");
}
}
}
Hasil eksekusinya adalah
Copy kode kodenya sebagai berikut:
22:30 $javaTestUtama
sebuah instance String di SubCaller
memeriksa
Karena sudah diperkenalkan di atas, maka saya hanya akan memposting hasil dekompilasinya di sini.
Copy kode kodenya sebagai berikut:
22:30 $ javap -c TesUtama
Dikompilasi dari "TestMain.java"
kelas publik TesUtama {
TesUtamaUtama();
Kode:
0: memuat_0
1: invokespecial #1 // Metode java/lang/Object."<init>":()V
4: kembali
public static void main(java.lang.String[]);
Kode:
0: baru #2 // kelas java/lang/String
3: bodoh
4: invokespecial #3 // Metode java/lang/String."<init>":()V
7: toko_1
8: #4 baru // kelas TestMain$SubCaller
11: bodoh
12: invokespecial #5 // Metode TestMain$SubCaller."<init>":()V
15: toko_2
16: memuat_2
17: memuat_1
18: invokevirtual #6 // Metode TestMain$Caller.call:(Ljava/lang/String;)V
21: kembali
}
Pertanyaan penasaran
Apakah tidak mungkin menggunakan pengikatan dinamis?
Faktanya, secara teori, pengikatan metode tertentu juga dapat dilakukan dengan pengikatan statis. Misalnya:
Copy kode kodenya sebagai berikut:
public static void main(String[] args) {
String str = String baru();
pemanggil terakhir callerSub = SubCaller baru();
callerSub.call(str);
}
Misalnya, di sini callerSub menampung objek subCaller dan variabel callerSub bersifat final, dan metode panggilan segera dieksekusi. Secara teori, kompiler dapat mengetahui bahwa metode panggilan SubCaller harus dipanggil dengan analisis kode yang memadai.
Tapi kenapa tidak ada pengikatan statis?
Asumsikan bahwa Penelepon kita mewarisi kelas BaseCaller dari kerangka kerja tertentu, yang mengimplementasikan metode panggilan, dan BaseCaller mewarisi dari SuperCaller. Metode panggilan juga diterapkan di SuperCaller.
Asumsikan BaseCaller dan SuperCaller dalam kerangka tertentu 1.0
Copy kode kodenya sebagai berikut:
kelas statis SuperCaller {
panggilan kekosongan publik(Obj objek) {
System.out.println("sebuah instance Objek di SuperCaller");
}
}
kelas statis BaseCaller memperluas SuperCaller {
panggilan kekosongan publik(Obj objek) {
System.out.println("sebuah instance Objek di BaseCaller");
}
}
Kami menerapkan ini menggunakan kerangka 1.0. Penelepon mewarisi BaseCaller dan memanggil metode super.call.
Copy kode kodenya sebagai berikut:
kelas publik TesUtama {
public static void main(String[] args) {
Objek obj = Objek baru();
SuperCaller callerSub = SubCaller baru();
callerSub.call(obj);
}
pemanggil kelas statis memperluas BaseCaller{
panggilan kekosongan publik(Obj objek) {
System.out.println("sebuah instance Objek di Caller");
super.panggilan(obj);
}
panggilan kekosongan publik(String str) {
System.out.println("sebuah instance String di dalam Caller");
}
}
SubCaller kelas statis memperluas Penelepon {
@Mengesampingkan
panggilan kekosongan publik(Obj objek) {
System.out.println("sebuah instance Objek di SubCaller");
}
@Mengesampingkan
panggilan kekosongan publik(String str) {
System.out.println("sebuah instance String di dalam SubCaller");
}
}
}
Kemudian kami mengkompilasi file kelas berdasarkan versi 1.0 kerangka ini. Dengan asumsi bahwa pengikatan statis dapat menentukan bahwa super.call dari Caller di atas diimplementasikan sebagai BaseCaller.call.
Kemudian kita asumsikan lagi bahwa BaseCaller tidak menulis ulang metode panggilan SuperCaller di versi 1.1 framework ini. Kemudian asumsi di atas bahwa implementasi panggilan yang dapat terikat secara statis akan menimbulkan masalah di versi 1.1, karena super.call harus menggunakan SuperCall di versi tersebut. 1.1. Implementasi metode panggilan, daripada berasumsi bahwa implementasi metode panggilan BaseCaller ditentukan oleh pengikatan statis.
Oleh karena itu, beberapa hal yang sebenarnya dapat diikat secara statis, hanya diikat secara dinamis dengan mempertimbangkan keamanan dan konsistensi.
Inspirasi optimasi didapat?
Karena pengikatan dinamis perlu menentukan versi implementasi metode atau variabel mana yang akan dieksekusi saat runtime, hal ini lebih memakan waktu daripada pengikatan statis.
Oleh karena itu, tanpa mempengaruhi desain keseluruhan, kita dapat mempertimbangkan untuk memodifikasi metode atau variabel dengan private, static atau final.