Saat mempertimbangkan inisialisasi kelas, kita semua tahu bahwa ketika melakukan inisialisasi subkelas, jika kelas induk tidak diinisialisasi, subkelas harus diinisialisasi terlebih dahulu. Namun, hal -hal tidak sesederhana satu kalimat.
Pertama, mari kita lihat kondisi untuk inisialisasi yang memicu di Java:
(1) Saat menggunakan objek baru untuk instantiate dan mengakses data dan metode statis, yaitu, ketika menemukan instruksi: baru, getstatic/putstatic dan Invokestatic;
(2) saat menggunakan refleksi untuk memanggil kelas;
(3) Ketika menginisialisasi kelas, jika kelas induk belum diinisialisasi, inisialisasi kelas induk akan dipicu terlebih dahulu;
(4) kelas tempat metode utama masuk dieksekusi berada;
(5) kelas di mana pegangan metode terletak di dukungan bahasa dinamis JDK1.7, jika inisialisasi tidak dipicu;
Setelah kompilasi, metode <linit> dihasilkan, dan inisialisasi kelas dilakukan dalam metode ini. Metode ini hanya dijalankan, dan JVM memastikan ini dan melakukan kontrol sinkronisasi;
Di antara mereka, kondisi (3), dari sudut pandang panggilan metode, adalah subclass <Clinit> yang secara rekursif menyebut kelas induk <Clinit> di awal, yang mirip dengan fakta bahwa pertama -tama kita harus memanggil konstruktor kelas induk dalam konstruktor subkelas;
Tetapi harus dicatat bahwa "memicu" tidak menyelesaikan inisialisasi, yang berarti bahwa dimungkinkan bahwa inisialisasi subclass akan berakhir di muka sebelum inisialisasi kelas induk, yang merupakan tempat "bahaya" berada.
1. Contoh inisialisasi kelas:
Dalam contoh ini, saya menggunakan kelas periferal untuk berisi 2 kelas anggota statis dengan hubungan warisan. Karena inisialisasi kelas periferal dan kelas anggota statis tidak memiliki hubungan sebab akibat, aman dan nyaman untuk menunjukkannya seperti ini;
Parent Class A dan Child Class B masing -masing berisi fungsi utama. Dari kondisi pemicu di atas (4), dapat dilihat bahwa jalur inisialisasi kelas yang berbeda dipicu dengan menyebut dua fungsi utama ini masing -masing;
Masalah dengan contoh ini adalah bahwa kelas induk berisi referensi statis kelas anak dan menginisialisasi pada definisi:
wrapperclass kelas publik {private static class a {static {System.out.println ("Kelas inisialisasi mulai ..."); } // Kelas induk berisi referensi statis dari kelas anak private static b b = new b (); Int statis yang dilindungi aint = 9; static {System.out.println ("Kelas AKHIR AKULASI ..."); } public static void main (string [] args) {}} private static class B memperluas {static {system.out.println ("Class B Inisialisasi Mulai ..."); } // Domain subkelas tergantung pada domain kelas induk private static int bint = 9 + a.aint; public b () {// Domain statis konstruktor tergantung pada kelas System.out.println ("Panggilan Konstruktor untuk Kelas B" + "Nilai Bint" + Bint); } static {System.out.println ("Inisialisasi kelas B berakhir ..." + "Nilai Aint:" + Bint); } public static void main (string [] args) {}}} Skenario 1: Hasil Output Saat entri adalah fungsi utama dari kelas B:
/** * Kelas A Inisialisasi dimulai ... * Konstruktor Kelas B menyebut nilai Bint 0 * Kelas A Inisialisasi berakhir ... * Inisialisasi Kelas B dimulai ... * Inisialisasi Kelas B berakhir ... Nilai Aint: 18 *//
Analisis: Dapat dilihat bahwa panggilan fungsi utama memicu inisialisasi kelas B dan memasuki metode <linit> Kelas B. Kelas A, sebagai kelas induknya, mulai menginisialisasi terlebih dahulu dan memasuki metode <Clinit> dari A, dan ada pernyataan baru B (); Pada saat ini, B akan dipakai, yang sudah ada di <linit> Kelas B. Utas utama telah memperoleh kunci dan mulai melaksanakan <linit> Kelas B. Kami mengatakan pada awalnya bahwa JVM akan memastikan bahwa metode inisialisasi kelas hanya dieksekusi sekali. Setelah menerima instruksi baru, JVM tidak akan memasukkan metode <linit> Kelas B lagi tetapi akan dipakai secara langsung. Namun, pada saat ini, Kelas B belum menyelesaikan inisialisasi kelas, sehingga Anda dapat melihat bahwa nilai Bint adalah 0 (ini 0 adalah inisialisasi nol yang dilakukan setelah mengalokasikan memori area metode selama tahap persiapan pemuatan kelas);
Oleh karena itu, dapat disimpulkan bahwa kelas induk berisi domain statis jenis anak dan melakukan tindakan penugasan, yang dapat menyebabkan instantiasi subkelas dilakukan sebelum inisialisasi kelas selesai;
Skenario 2: Hasil Output Ketika entri adalah fungsi utama dari Kelas A:
/** * Kelas A Inisialisasi dimulai ... * Inisialisasi Kelas B dimulai ... * Inisialisasi Kelas B berakhir ... Nilai Aint: 9 * Konstruktor Kelas B menyebut nilai Bint 9 * Kelas A Inisialisasi berakhir ... *//
Analisis: Setelah analisis skenario 1, kita tahu bahwa memicu inisialisasi kelas A dengan inisialisasi kelas B akan menyebabkan instantiasi variabel kelas B di kelas A dilakukan sebelum inisialisasi kelas B selesai. Jadi, jika Kelas A diinisialisasi terlebih dahulu, dapatkah Kelas B dipicu terlebih dahulu saat instantiasi variabel kelas, sehingga inisialisasi dibuat sebelum instantiasi? Jawabannya adalah ya, tapi masih ada masalah.
Menurut output, kita dapat melihat bahwa inisialisasi kelas B dilakukan sebelum inisialisasi kelas A selesai, yang menyebabkan variabel seperti variabel kelas tidak diinisialisasi hanya setelah Kelas B diinisialisasi, sehingga nilai tidak diperoleh oleh domain Bint di Kelas B adalah "0", daripada "18" seperti yang kami harapkan;
Kesimpulan: Singkatnya, dapat disimpulkan bahwa sangat berbahaya untuk memasukkan variabel kelas jenis subkelas di kelas induk dan instantiate mereka saat mendefinisikannya. Situasi spesifik mungkin tidak semudah contoh. Metode panggilan untuk menetapkan nilai pada definisi juga berbahaya. Bahkan jika Anda ingin memasukkan domain statis dari jenis subkelas, Anda juga harus menetapkan nilai melalui metode statis, karena JVM dapat memastikan bahwa semua tindakan inisialisasi diselesaikan sebelum metode statis disebut (tentu saja, jaminan ini adalah bahwa Anda tidak boleh memasukkan statis B B = B () baru (); perilaku inisialisasi tersebut);
2. Contoh Instantiated:
Pertama, Anda perlu mengetahui proses pembuatan objek:
(1) Saat menemukan instruksi baru, periksa apakah kelas telah menyelesaikan pemuatan, verifikasi, persiapan, penguraian, dan inisialisasi (proses penguraian adalah untuk menguraikan referensi simbol ke dalam referensi langsung, seperti nama metode adalah referensi simbol, yang dapat dilakukan ketika menggunakan referensi simbol ini setelah inisialisasi selesai, tepatnya untuk mendukung pengikat dinamis), proses ini diselesaikan; proses ini diselesaikan; Binding dinamis ini), proses ini diselesaikan; Proses ini diselesaikan; Proses ini diselesaikan; Binding Dinamis), proses ini diselesaikan; Proses ini diselesaikan;
(2) Alokasikan memori, gunakan daftar gratis atau metode tabrakan pointer, dan "nol" memori yang baru dialokasikan. Oleh karena itu, semua variabel instance diinisialisasi ke 0 secara default (direferensikan sebagai null) di tautan ini;
(3) Jalankan metode <Inin>, termasuk memeriksa panggilan ke metode <Inin> (konstruktor) dari kelas induk, tindakan penugasan yang ditentukan oleh variabel instan, instantiator dieksekusi dalam instantiator, dan akhirnya memanggil tindakan dalam konstruktor.
Contoh ini mungkin lebih terkenal, yaitu, melanggar "jangan sebut metode yang diterbangkan dalam konstruktor, metode klon dan metode readObject". Alasannya adalah bahwa polimorfisme di Java, yaitu ikatan dinamis.
Konstruktor Parent Class A berisi metode yang dilindungi, dan Kelas B adalah subkelasnya.
kelas publik WrongInstantiation {private static class a {public a () {dosomething (); } protected void dosomething () {System.out.println ("dosomething A"); }} private static class B memperluas A {private int bint = 9; @Override Protected void dosomething () {System.out.println ("b's dosomething, bint:" + bint); }} public static void main (string [] args) {b b = new b (); }}Hasil output:
/** * B's dosomething, bint: 0 */
Analisis: Pertama -tama, Anda perlu tahu bahwa ketika tidak ada tampilan, kompiler Java akan menghasilkan konstruktor default dan memanggil konstruktor kelas induk di awal. Oleh karena itu, konstruktor Kelas B akan memanggil konstruktor Kelas A yang pertama di awal.
Metode yang dilindungi dosomething dipanggil di Kelas A. Dari hasil output, kami melihat bahwa implementasi metode subclass sebenarnya disebut, dan instantiasi subkelas belum dimulai, jadi Bint bukan 9 seperti "diharapkan" tetapi 0;
Ini karena pengikatan dinamis, dosomething adalah metode yang dilindungi, sehingga dipanggil melalui arahan Invokevirtual, yang menemukan implementasi metode yang sesuai berdasarkan jenis instance objek (berikut adalah objek instan B, dan metode yang sesuai adalah implementasi metode kelas B), sehingga hasilnya adalah.
Kesimpulan: Seperti yang disebutkan sebelumnya, "Jangan sebut metode yang diterbitkan dalam konstruktor, metode klon dan metode readObject".
Di atas adalah dua "ladang ranjau" dalam inisialisasi kelas Java dan instantiasi yang diperkenalkan kepada Anda. Saya berharap ini akan membantu untuk pembelajaran semua orang.