Dengan kelas anonim, Anda sebenarnya mendeklarasikan kelas bersarang "tanpa nama". Untuk kelas bertingkat, kompiler menghasilkan kelas publik mandiri baru dengan konstruktor yang akan mengambil semua variabel yang digunakan sebagai argumen (untuk kelas bertingkat "bernama", ini selalu merupakan turunan dari kelas asli / tertutup). Ini dilakukan karena lingkungan runtime tidak memiliki gagasan tentang kelas bersarang, sehingga perlu ada konversi (otomatis) dari kelas bersarang ke kelas mandiri.
Ambil kode ini misalnya:
public class EnclosingClass {
public void someMethod() {
String shared = "hello";
new Thread() {
public void run() {
// this is not valid, won't compile
System.out.println(shared); // this instance expects shared to point to the reference where the String object "hello" lives in heap
}
}.start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}
Itu tidak akan berhasil, karena inilah yang dilakukan oleh kompiler di bawah tenda:
public void someMethod() {
String shared = "hello";
new EnclosingClass$1(shared).start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
Kelas anonim asli digantikan oleh beberapa kelas mandiri yang dihasilkan oleh kompiler (kode tidak tepat, tetapi harus memberi Anda ide yang baik):
public class EnclosingClass$1 extends Thread {
String shared;
public EnclosingClass$1(String shared) {
this.shared = shared;
}
public void run() {
System.out.println(shared);
}
}
Seperti yang Anda lihat, kelas mandiri memegang referensi ke objek bersama, ingat bahwa semua yang ada di java adalah nilai per nilai, jadi meskipun variabel referensi 'dibagi' di EnclosingClass akan diubah, contoh yang ditunjukkannya tidak dimodifikasi. , dan semua variabel referensi lainnya yang menunjuk ke sana (seperti yang ada di kelas anonim: Melampirkan $ 1), tidak akan mengetahui hal ini. Ini adalah alasan utama kompilator memaksa Anda untuk mendeklarasikan variabel 'dibagikan' ini sebagai final, sehingga jenis perilaku ini tidak akan membuatnya menjadi kode yang sudah berjalan.
Sekarang, inilah yang terjadi ketika Anda menggunakan variabel instan di dalam kelas anonim (inilah yang harus Anda lakukan untuk menyelesaikan masalah Anda, pindahkan logika Anda ke metode "instance" atau konstruktor suatu kelas):
public class EnclosingClass {
String shared = "hello";
public void someMethod() {
new Thread() {
public void run() {
System.out.println(shared); // this is perfectly valid
}
}.start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
}
Ini mengkompilasi dengan baik, karena kompiler akan mengubah kode, sehingga kelas baru yang dihasilkan Menutupi $ 1 akan menyimpan referensi ke instance EnclosingClass di mana ia dipakai (ini hanya representasi, tetapi harus membuat Anda pergi):
public void someMethod() {
new EnclosingClass$1(this).start();
// change the reference 'shared' points to, with a new value
shared = "other hello";
System.out.println(shared);
}
public class EnclosingClass$1 extends Thread {
EnclosingClass enclosing;
public EnclosingClass$1(EnclosingClass enclosing) {
this.enclosing = enclosing;
}
public void run() {
System.out.println(enclosing.shared);
}
}
Seperti ini, ketika variabel referensi 'dibagikan' di EnclosingClass akan dipindahkan, dan ini terjadi sebelum panggilan ke Thread # run (), Anda akan melihat "halo lainnya" dicetak dua kali, karena sekarang variabel penutup EnclosingClass $ 1 # akan menyimpan referensi ke objek kelas yang dideklarasikan, jadi perubahan pada atribut apa pun pada objek itu akan terlihat oleh instance dari EnclosingClass $ 1.
Untuk informasi lebih lanjut tentang hal ini, Anda dapat melihat posting blog yang sangat baik ini (tidak ditulis oleh saya): http://kevinboone.net/java_inner.html