Dalam pemrograman Java, jika suatu anggota dimodifikasi menggunakan kata kunci private, hanya kelas di mana anggota tersebut berada dan metode kelas tersebut yang dapat digunakan, dan tidak ada kelas lain yang dapat mengakses anggota pribadi tersebut.
Fungsi dasar pengubah privat dijelaskan di atas. Hari ini kita akan mempelajari kegagalan fungsi privat.
Kelas dalam Java
Saya percaya banyak orang telah menggunakan kelas dalam di Jawa. Java memungkinkan satu kelas untuk didefinisikan dalam kelas lain. Kelas di dalam kelas adalah kelas dalam, juga disebut kelas bersarang. Implementasi kelas dalam yang sederhana dapat disalin sebagai berikut:
kelas Kelas Luar {
kelas Kelas Dalam{
}
}
Pertanyaan hari ini terkait dengan kelas internal Java, dan hanya melibatkan sebagian dari pengetahuan kelas internal yang terkait dengan penelitian artikel ini. Artikel khusus berikutnya tentang kelas internal Java akan diperkenalkan.
Kegagalan pertama kali?
Skenario yang sering kita gunakan dalam pemrograman adalah mengakses variabel anggota privat atau metode kelas eksternal di kelas internal. Diimplementasikan sebagai kode berikut.
Copy kode kodenya sebagai berikut:
kelas publik Kelas Luar {
bahasa String pribadi = "en";
wilayah String pribadi = "AS";
kelas publik Kelas Dalam {
public void printOuterClassPrivateFields() {
Bidang string = "bahasa=" + bahasa + ";wilayah=" + wilayah;
System.out.println(bidang);
}
}
public static void main(String[] args) {
Kelas Luar luar = Kelas Luar baru();
OuterClass.InnerClass inner = luar.new InnerClass();
inner.printOuterClassPrivateFields();
}
}
Mengapa demikian? Bukankah anggota yang dimodifikasi secara pribadi hanya dapat diakses oleh kelas yang diwakilinya? Apakah privasi benar-benar tidak valid?
Apakah kompiler menyebabkan masalah?
Mari gunakan perintah javap untuk melihat dua file kelas yang dihasilkan.
Salinan kode hasil dekompilasi OuterClass adalah sebagai berikut:
15:30 $ javap -c Kelas Luar
Dikompilasi dari "OuterClass.java"
kelas publik OuterClass memperluas java.lang.Object{
Kelas Luar publik();
Kode:
0: memuat_0
1: invokespecial #11; //Metode java/lang/Objek."<init>":()V
4: memuat_0
5: ldc #13; //String en
7: putfield #15; //Bahasa bidang:Ljava/lang/String;
10: memuat_0
11: ldc #17; //String AS
13: putfield #19; //Wilayah bidang:Ljava/lang/String;
16: kembali
public static void main(java.lang.String[]);
Kode:
0: baru #1; //kelas LuarKelas
3: bodoh
4: panggilkhusus #27; //Metode "<init>":()V
7: toko_1
8: baru #28; //kelas Kelas Luar$Kelas Dalam
11: bodoh
12: memuat_1
13: bodoh
14: memanggil virtual #30; //Metode java/lang/Object.getClass:()Ljava/lang/Class;
17: Pop
18: invokespecial #34; //Metode OuterClass$InnerClass."<init>":(LOuterClass;)V
21: toko_2
22: memuat_2
23: memanggilvirtual #37; //Metode OuterClass$InnerClass.printOuterClassPrivateFields:()V
26: kembali
akses java.lang.String statis$0(OuterClass);
Kode:
0: memuat_0
1: getfield #15; //Bahasa bidang:Ljava/lang/String;
4: kembali
akses java.lang.String statis$1(OuterClass);
Kode:
0: memuat_0
1: getfield #19; //Wilayah bidang:Ljava/lang/String;
4: kembali
}
Hah? Tidak, kami tidak mendefinisikan kedua metode ini di OuterClass
akses java.lang.String statis$0(OuterClass);
Kode:
0: memuat_0
1: getfield #15; //Bahasa bidang:Ljava/lang/String;
4: kembali
akses java.lang.String statis$1(OuterClass);
Kode:
0: memuat_0
1: getfield #19; //Wilayah bidang:Ljava/lang/String;
4: kembali
}
Dilihat dari komentar yang diberikan, access$0 mengembalikan atribut bahasa dari outerClass; access$1 mengembalikan atribut wilayah dari outerClass. Dan kedua metode menerima instance OuterClass sebagai parameter. Mengapa kedua metode ini dihasilkan dan apa fungsinya? Mari kita lihat hasil dekompilasi inner class dan kita akan mengetahuinya.
Hasil dekompilasi OuterClass$InnerClass
Copy kode kodenya sebagai berikut:
15:37 $ javap -c Kelas Luar/$Kelas Dalam
Dikompilasi dari "OuterClass.java"
kelas publik OuterClass$InnerClass meluas java.lang.Object{
OuterClass terakhir ini$0;
Kelas Luar publik$Kelas Dalam(Kelas Luar);
Kode:
0: memuat_0
1: memuat_1
2: putfield #10; //Kolom ini$0:LOuterClass;
5: memuat_0
6: invokespecial #12; //Metode java/lang/Object."<init>":()V
9: kembali
public void printOuterClassPrivateFields();
Kode:
0: baru #20; //kelas java/lang/StringBuilder
3: bodoh
4: ldc #22; //Bahasa string=
6: panggilkhusus #24; //Metode java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
9: memuat_0
10: getfield #10; //Kolom ini$0:LOuterClass;
13: pemanggilan statis #27; //Metode OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
16: pemanggilan virtual #33; //Metode java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #37; //String ;wilayah=
21: pemanggilan virtual #33; //Metode java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: memuat_0
25: getfield #10; //Kolom ini$0:LOuterClass;
28: pemanggilan statis #39; //Metode OuterClass.access$1:(LOuterClass;)Ljava/lang/String;
31: pemanggilan virtual #33; //Metode java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: pemanggilan virtual #42; //Metode java/lang/StringBuilder.toString:()Ljava/lang/String;
37: toko_1
38: getstatic #46; //Bidang java/lang/System.out:Ljava/io/PrintStream;
41: memuat_1
42: memanggil virtual #52; //Metode java/io/PrintStream.println:(Ljava/lang/String;)V
45: kembali
}
Kode berikut memanggil kode akses$0, yang tujuannya adalah untuk mendapatkan atribut bahasa privat dari OuterClass.
Copy kode kodenya sebagai berikut:
13: pemanggilan statis #27; //Metode OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
Kode berikut memanggil kode akses$1, yang tujuannya adalah untuk mendapatkan atribut privat wilayah dari OutherClass.
Copy kode kodenya sebagai berikut:
28: pemanggilan statis #39; //Metode OuterClass.access$1:(LOuterClass;)Ljava/lang/String;
Catatan: Ketika kelas dalam dibangun, referensi kelas luar akan diteruskan dan digunakan sebagai atribut kelas dalam, sehingga kelas dalam akan menyimpan referensi ke kelas luarnya.
this$0 adalah referensi kelas eksternal yang dimiliki oleh kelas dalam. Referensi diteruskan dan ditetapkan melalui metode konstruktor.
Copy kode kodenya sebagai berikut:
OuterClass terakhir ini$0;
Kelas Luar publik$Kelas Dalam(Kelas Luar);
Kode:
0: memuat_0
1: memuat_1
2: putfield #10; //Kolom ini$0:LOuterClass;
5: memuat_0
6: invokespecial #12; //Metode java/lang/Object."<init>":()V
9: kembali
ringkasan
Bagian privat ini sepertinya tidak valid, namun nyatanya tidak valid, karena ketika kelas dalam memanggil properti privat dari kelas eksternal, eksekusi sebenarnya adalah memanggil metode statis dari properti yang dihasilkan oleh kompiler (yaitu akses $0, akses$1, dll.) untuk mendapatkan nilai atribut ini. Ini semua adalah pemrosesan khusus oleh kompiler.
Apakah kali ini tidak berhasil?
Jika cara penulisan di atas sudah sangat umum digunakan, maka cara penulisan ini jarang digunakan, namun bisa dijalankan.
Copy kode kodenya sebagai berikut:
kelas publik AnotherOuterClass {
public static void main(String[] args) {
InnerClass inner = new AnotherOuterClass().new InnerClass();
System.out.println("InnerClass Filed = " + inner.x);
}
kelas Kelas Dalam {
int pribadi x = 10;
}
}
Seperti di atas, gunakan javap untuk mendekompilasi dan melihatnya. Namun kali ini mari kita lihat hasil InnerClass. Salin kodenya sebagai berikut:
16:03 $ javap -c AnotherOuterClass/$InnerClass
Dikompilasi dari "AnotherOuterClass.java"
kelas AnotherOuterClass$InnerClass memperluas java.lang.Object{
final AnotherOuterClass ini$0;
AnotherOuterClass$InnerClass(AnotherOuterClass);
Kode:
0: memuat_0
1: memuat_1
2: putfield #12; //Kolom ini$0:LAnotherOuterClass;
5: memuat_0
6: invokespecial #14; //Metode java/lang/Objek."<init>":()V
9: memuat_0
10: bipush 10
12: bidang put #17; // Bidang x:I
15: kembali
akses int statis$0(AnotherOuterClass$InnerClass);
Kode:
0: memuat_0
1: getfield #17; //Bidang x:I
4: kembali
}
Hal ini terjadi lagi, dan kompiler secara otomatis membuat metode pintu belakang access$0 untuk mendapatkan atribut privat satu kali untuk mendapatkan nilai x.
Salinan kode hasil dekompilasi AnotherOuterClass.class adalah sebagai berikut:
16:08 $ javap -c AnotherOuterClass
Dikompilasi dari "AnotherOuterClass.java"
kelas publik AnotherOuterClass memperluas java.lang.Object{
publik AnotherOuterClass();
Kode:
0: memuat_0
1: invokespecial #8; //Metode java/lang/Objek."<init>":()V
4: kembali
public static void main(java.lang.String[]);
Kode:
0: baru #16; //kelas AnotherOuterClass$InnerClass
3: bodoh
4: baru #1; //kelas AnotherOuterClass
7: bodoh
8: panggilkhusus #18; //Metode "<init>":()V
11: bodoh
12: memanggilvirtual #19; //Metode java/lang/Object.getClass:()Ljava/lang/Class;
15: muncul
16: invokespecial #23; //Metode AnotherOuterClass$InnerClass."<init>":(LAnotherOuterClass;)V
19: toko_1
20: getstatic #26; //Bidang java/lang/System.out:Ljava/io/PrintStream;
23: baru #32; //kelas java/lang/StringBuilder
26: bodoh
27: ldc #34; //String Kelas Dalam Diarsipkan =
29: panggilkhusus #36; //Metode java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
32: memuat_1
33: pemanggilan statis #39; //Metode AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I
36: pemanggilan virtual #43; //Metode java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
39: pemanggilan virtual #47; //Metode java/lang/StringBuilder.toString:()Ljava/lang/String;
42: memanggil virtual #51; //Metode java/io/PrintStream.println:(Ljava/lang/String;)V
45: kembali
}
Panggilan ini adalah operasi kelas eksternal untuk mendapatkan properti pribadi x melalui instance kelas internal.
33: pemanggilan statis #39; //Metode AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I
Ringkasan lainnya
Diantaranya, dokumentasi resmi Java memiliki kalimat ini. Salin kode sebagai berikut:
jika anggota atau konstruktor dinyatakan pribadi, maka akses diizinkan jika dan hanya jika akses tersebut terjadi di dalam badan kelas tingkat atas (§7.6) yang menyertakan deklarasi anggota atau konstruktor.
Artinya jika anggota dan konstruktor (dari kelas dalam) disetel ke pengubah privat, akses diperbolehkan jika dan hanya jika kelas luarnya.
Bagaimana mencegah anggota privat kelas dalam diakses oleh pihak luar
Saya yakin setelah membaca dua bagian di atas, Anda akan merasa sangat sulit bagi anggota privat kelas internal untuk diakses oleh kelas eksternal. Siapa yang ingin kompilernya "usil"? Selesai. Yaitu dengan menggunakan kelas dalam anonim.
Karena tipe objek mRunnable adalah Runnable, bukan tipe kelas dalam anonim (yang tidak bisa kita dapatkan secara normal), dan tidak ada atribut x di Runnble, mRunnable.x tidak diperbolehkan.
Copy kode kodenya sebagai berikut:
kelas publik PrivateToOuter {
Dapat dijalankan mRunnable = baru Dapat dijalankan(){
int pribadi x=10;
@Mengesampingkan
menjalankan kekosongan publik() {
Sistem.keluar.println(x);
}
};
public static void main(String[] args){
PrivateToOuter p = PrivateToOuter baru();
//System.out.println("kelas anonim private diajukan= "+ p.mRunnable.x); //tidak diperbolehkan
p.mRunnable.run(); // diperbolehkan
}
}
Ringkasan akhir
Dalam artikel ini, privat tampaknya tidak valid di permukaan, namun kenyataannya tidak. Sebaliknya, properti privat diperoleh melalui metode tidak langsung saat memanggil.
Kelas internal Java menampung aplikasi untuk kelas eksternal ketika dibangun, tetapi C++ tidak.