1. Poin Pengetahuan Tentang Kontrol Objek dan Memori
1. Proses inisialisasi variabel Java, termasuk variabel lokal, variabel anggota (variabel instance dan variabel kelas).
2. Dalam hubungan warisan, ketika jenis waktu kompilasi dan jenis run-time berbeda dari jenis waktu kompilasi dari variabel referensi objek yang digunakan, ada perbedaan dalam properti dan metode mengakses objek.
3. Karakteristik pengubah akhir.
2. Proses Divisi dan Inisialisasi Variabel Java
Variabel program Java dapat dibagi secara kasar menjadi variabel anggota dan variabel lokal. Variabel anggota dapat dibagi menjadi variabel instan (variabel non-statis) dan variabel kelas (variabel statis). Secara umum, variabel lokal yang kami temui akan muncul dalam situasi berikut:
(1) Parameter formal: Variabel lokal yang ditentukan dalam tanda tangan metode ditetapkan oleh penelepon dan menghilang saat metode berakhir.
(2) Variabel lokal dalam metode ini: Variabel lokal yang ditentukan dalam metode harus diinisialisasi (menetapkan nilai awal) dalam metode ini, dan menghilang ketika inisialisasi variabel dimulai dan berakhir.
(3) Variabel lokal dalam blok kode: Variabel lokal yang ditentukan dalam blok kode harus diinisialisasi (tetapkan nilai awal) yang harus ditampilkan dalam blok kode. Mereka akan berlaku ketika inisialisasi selesai dan mati saat blok kode berakhir.
paket com.zlc.array; Testfield kelas publik {{string b; // Jika tidak diinisialisasi, kompiler akan melaporkan variabel lokal B mungkin belum diinisialisasi system.out.println (b); } public static void main (string [] args) {int a; // Jika tidak diinisialisasi, kompiler akan melaporkan variabel lokal a mungkin belum diinisialisasi system.out.println (a); }} Variabel anggota yang dimodifikasi dengan statis adalah variabel kelas, yang termasuk dalam kelas itu sendiri. Variabel anggota yang tidak dimodifikasi dengan statis adalah variabel instance. Contoh milik kelas ini, dalam JVM yang sama, setiap kelas hanya dapat sesuai dengan satu objek kelas, tetapi setiap kelas dapat membuat beberapa objek Java. ;
Proses inisialisasi variabel instan: Dari perspektif sintaks, program dapat melakukan inisialisasi variabel instance di tiga tempat:
(1) Tentukan nilai awal saat mendefinisikan variabel instan.
(2) Tentukan nilai awal untuk variabel instance dalam blok non-statis.
(3) Tentukan nilai awal untuk variabel instance dalam konstruktor.
Di antara mereka, waktu inisialisasi dari dua metode (1) dan (2) lebih awal dari (3) dalam konstruktor, dan dua perintah inisialisasi (1) dan (2) ditentukan dalam urutan mereka diatur dalam kode sumber.
paket com.zlc.array; Testfield kelas publik {public testfield (int age) {System.out.println ("Inisialisasi this.age di konstruktor ="+this.age); this.age = usia; } {System.out.println ("Inisialisasi dalam blok non-statis"); usia = 22; } // Inisialisasi int usia = 15; public static void main (string [] args) {testfield field = new testfield (24); System.out.println ("Usia Akhir ="+Field.Age); }} Hasil jangka adalah: inisialisasi this.age = 15 dalam konstruktor inisialisasi di blok non-statis
Usia Akhir = 24
Jika Anda tahu cara menggunakan JAVAP, Anda dapat menggunakan javap -c xxxx (file kelas) untuk melihat bagaimana kelas Java dikompilasi.
Saat mendefinisikan variabel instan, tentukan nilai awal. Di blok inisialisasi, status pernyataan yang menentukan nilai awal untuk variabel instance sama. Setelah kompiler dikompilasi dan diproses, mereka semua disebutkan dalam konstruktor. Usia int yang disebutkan di atas = 15 akan dibagi menjadi dua langkah berikut untuk mengeksekusi:
1) usia int; Saat membuat objek Java, sistem mengalokasikan memori ke objek sesuai dengan pernyataan.
2) usia = 15; Pernyataan ini akan diekstraksi ke dalam konstruktor kelas Java dan dieksekusi.
Proses Inisialisasi Variabel Kelas: Dari perspektif sintaks, suatu program dapat menginisialisasi dan menetapkan nilai ke variabel kelas dari dua tempat.
(1) Tentukan nilai awal saat mendefinisikan variabel kelas.
(2) Tentukan nilai awal untuk variabel kelas dalam blok statis.
Dua perintah eksekusi sama dengan pengaturan mereka dalam kode sumber. Mari berikan contoh abnormal kecil:
paket com.zlc.array; class teststatic {// class anggota demo teststatic instatic final static teststatic demo = new teststatic (15); // Kelas Anggota Statis Int Usia = 20; // Instance Variable Curage Int Curage; Public Teststatic (int Years) {// TODO Kurage Stub Konstruktor yang Dihasilkan Otomatis = Usia - Tahun; }} tes kelas publik {public static void main (string [] args) {system.out.println (teststatic.demo.curage); Teststatic staticDemo = teststatic baru (15); System.out.println (StaticDemo.Curage); }} Hasil output dicetak dalam dua baris. Salah satunya adalah mencetak variabel instance dari demo atribut kelas teststatic, dan yang kedua adalah untuk mengeluarkan atribut instance dari teststatic melalui staticDemo objek Java. Menurut proses inisialisasi variabel instance dan variabel kelas yang kami analisis di atas, kami dapat menyimpulkannya:
1) Pada tahap pertama inisialisasi, saat memuat kelas, mengalokasikan ruang memori untuk demo dan usia variabel kelas. Pada saat ini, nilai default demo dan usia masing -masing adalah nol dan 0.
2) Pada tahap kedua inisialisasi, program menetapkan nilai awal untuk demo dan usia secara berurutan. Teststatic (15) perlu memanggil konstruktor teststatic. Saat ini, usia = 0, jadi hasil pencetakannya -15. Ketika staticDemo diinisialisasi, usia telah ditugaskan ke 20, jadi hasil output adalah 5.
3. Perbedaan antara variabel anggota mewarisi dan metode warisan anggota dalam hubungan warisan
Saat membuat objek Java apa pun, program ini akan selalu memanggil blok non-statis dan konstruktor kelas induk dari kelas induk terlebih dahulu, dan akhirnya memanggil blok non-statis dan konstruktor kelas ini. Menyebut konstruktor kelas induk melalui konstruktor subclass umumnya dibagi menjadi dua situasi: satu adalah panggilan implisit, dan yang lainnya adalah tampilan super untuk memanggil konstruktor kelas induk.
Metode kelas anak dapat memanggil variabel instance dari kelas induk. Ini karena kelas anak mewarisi kelas induk dan akan mendapatkan variabel anggota dan metode kelas induk. Namun, metode kelas induk tidak dapat mengakses variabel instance dari kelas anak karena kelas induk tidak tahu kelas mana yang akan diwarisi dan jenis variabel anggota apa yang akan ditambahkan subkelasnya. Tentu saja, dalam beberapa contoh ekstrem, kelas induk masih dapat memanggil variabel kelas anak. Sebagai contoh: kelas anak menulis ulang metode kelas induk dan umumnya mencetak nilai default, karena variabel instance dari kelas anak belum diinisialisasi saat ini.
paket com.zlc.array; kelas ayah {int usia = 50; ayah publik () {// TODO Konstruktor Stub System.out.println (this.getClass ()); //this.sonmethod (); tidak dapat menghubungi info (); } public void info () {System.out.println (usia); }} kelas publik Son memperluas ayah {int usia = 24; Public Son (Int Age) {// TODO Konstruktor yang dihasilkan otomatis Stub ini.age = usia; } @Override public void info () {// TODO METODE AUTO-EKSIRATED Stub System.err.println (usia); } public static void main (string [] args) {new son (28); } // Metode khusus subkelas public void sonmethod () {System.out.println ("Metode Son"); }} Menurut inferensi normal kami, konstruktor kelas induk secara implisit dipanggil melalui subkelas, dan metode info () dipanggil dalam konstruktor kelas induk (Catatan: Saya tidak mengatakan bahwa kelas induk dipanggil). Secara teori, ini menghasilkan variabel instance usia dari kelas induk. Hasil cetak diharapkan 50, tetapi hasil output aktual adalah 0. Analisis alasannya:
1) Alokasi memori objek Java tidak selesai dalam konstruktor. Konstruktor hanya menyelesaikan proses penugasan inisialisasi. Artinya, sebelum memanggil konstruktor kelas induk, JVM telah mengklasifikasikan ruang memori untuk objek Son. Ruang ini menyimpan dua atribut usia, satu adalah usia subkelas dan yang lainnya adalah usia kelas induk.
2) Saat memanggil putra baru (28), objek ini saat ini mewakili objek yang merupakan putra subkelas. Kita dapat mencetak objek.getClass () dan mendapatkan hasil kelas com.zlc.array.son. Namun, proses inisialisasi saat ini dilakukan dalam konstruktor kelas induk, dan tidak dapat dipanggil melalui ini.sonMethod (), karena jenis kompilasi ini adalah ayah.
3) Ketika jenis waktu kompilasi dari variabel berbeda dari jenis runtime, ketika mengakses variabel instance dari objek referensi melalui variabel, nilai variabel instan ditentukan oleh jenis variabel yang dinyatakan. Namun, ketika metode instance dari objek yang dirujuknya melalui variabel, perilaku metode ditentukan oleh objek yang sebenarnya dirujuk. Oleh karena itu, metode info subclass disebut di sini, sehingga usia subkelas dicetak. Karena usia belum diinisialisasi segera, nilai standarnya adalah 0.
Dalam istilah awam, ketika tipe yang dinyatakan tidak konsisten dengan tipe baru yang sebenarnya, atribut yang digunakan adalah kelas induk dan metode yang disebut adalah kelas anak.
Melalui JAVAP -C, kita dapat lebih memahami secara langsung mengapa ada perbedaan besar antara atribut dan metode yang mewarisi. Jika kita menghapus metode ulang info dari anak sub -class pada contoh di atas, metode info kelas induk akan dipanggil saat ini, karena ketika menyusun, metode info kelas induk akan ditransfer ke subkelas, dan variabel anggota reputasi akan ditinggalkan di kelas induk dan tidak ditransfer. Dengan cara ini, subclass dan kelas induk memiliki variabel instance dengan nama yang sama. Jika subclass menulis ulang metode kelas induk dengan nama yang sama, metode subclass akan sepenuhnya menimpa metode kelas induk (seperti mengapa Java dirancang seperti ini, saya tidak terlalu jelas). Variabel dengan nama yang sama bisa ada dan tidak menimpa pada saat yang sama. Subkelas metode dengan nama yang sama akan sepenuhnya menimpa metode nama yang sama kelas induk.
Secara umum, untuk variabel referensi, ketika mengakses variabel instance dari objek yang dirujuknya melalui variabel, nilai variabel instance tergantung pada jenis ketika variabel dinyatakan, dan ketika mengakses metode objek yang dirujuknya melalui variabel, perilaku metode tergantung pada jenis objek yang sebenarnya dirujuk.
Akhirnya, saya akan memeriksanya dengan kasus kecil:
paket com.zlc.array; kelas hewan {int usia; hewan publik () {} hewan publik (usia int) {// TODO Konstruktor yang dihasilkan otomatis Stub this.age = usia; } void run () {System.out.println ("Animal Run"+usia); }} class dog memperluas hewan {int usia; Nama string; anjing publik (usia int, nama string) {// TODO Konstruktor yang dihasilkan otomatis Stub this.age = usia; this.name = name; } @Override void run () {System.out.println ("Dog Run"+usia); }} public class testextends {public static void main (string [] args) {hewan hewan = hewan baru (5); System.out.println (Animal.age); animal.run (); Anjing anjing = anjing baru (1, "xiaobai"); System.out.println (dog.age); dog.run (); Hewan hewan2 = anjing baru (11, "wangcai"); System.out.println (animal2.age); animal2.run (); Hewan hewan3; animal3 = anjing; System.out.println (animal3.age); animal3.run (); }} Jika Anda ingin memanggil metode kelas induk: Anda dapat memanggilnya melalui super, tetapi kata kunci super tidak merujuk ke objek apa pun, dan tidak dapat digunakan sebagai variabel referensi nyata. Teman yang tertarik dapat mempelajarinya sendiri.
Di atas adalah contoh variabel dan metode. Variabel kelas dan metode kelas jauh lebih sederhana, jadi menggunakan nama kelas secara langsung. Metode ini jauh lebih nyaman dan Anda tidak akan mengalami banyak masalah.
4. Penggunaan pengubah akhir (terutama penggantian makro)
(1) Inal dapat memodifikasi variabel. Setelah variabel yang dimodifikasi oleh final diberi nilai awal, itu tidak dapat ditetapkan lagi.
(2) Inal dapat memodifikasi metode ini, dan metode modifikasi akhir tidak dapat ditulis ulang.
(3) Inal dapat memodifikasi kelas, dan kelas yang dimodifikasi dengan final tidak dapat memperoleh subkelas.
Nilai awal yang ditentukan yang dimodifikasi oleh variabel oleh final harus ditampilkan:
Misalnya variabel yang dimodifikasi akhir, nilai awal hanya dapat ditetapkan pada tiga posisi yang ditentukan berikut.
(1) Tentukan nilai awal saat menentukan variabel instance akhir.
(2) Tentukan nilai awal untuk variabel instance akhir dalam blok non-statis.
(3) Tentukan nilai awal untuk variabel instance akhir dalam konstruktor.
Mereka pada akhirnya akan disebutkan dalam konstruktor untuk inisialisasi.
Untuk variabel kelas yang ditentukan dengan final: Nilai awal hanya dapat ditetapkan di dua tempat yang ditentukan.
(1) Tentukan nilai awal saat menentukan variabel kelas akhir.
(2) Tentukan nilai awal untuk variabel kelas akhir dalam blok statis.
Juga diproses oleh kompiler, tidak seperti variabel instance, variabel kelas semua disebutkan untuk menetapkan nilai awal dalam blok statis, sementara variabel instance disebutkan kepada konstruktor.
Ada fitur lain dari variabel kelas yang dimodifikasi berdasarkan final, yaitu "penggantian makro". Ketika variabel kelas yang dimodifikasi memenuhi nilai awal ketika mendefinisikan variabel, nilai awal dapat ditentukan selama kompilasi (misalnya: 18, "AAAA", 16.78 dan jumlah langsung lainnya), maka variabel kelas yang dimodifikasi dengan final bukanlah variabel, dan sistem akan memperlakukannya sebagai "variabel makro" (yang sering disebut konstan). Jika nilai awal dapat ditentukan selama kompilasi, itu tidak akan disebutkan dalam blok statis untuk inisialisasi, dan nilai awal akan langsung diganti dengan variabel akhir dalam definisi kelas. Mari berikan contoh usia dikurangi tahun:
paket com.zlc.array; class teststatic {// class anggota demo teststatic instatic final static teststatic demo = new teststatic (15); // usia anggota kelas statis statis usia = 20; // Instance Variable Curage Int Curage; Public Teststatic (int Years) {// TODO Kurage Stub Konstruktor yang Dihasilkan Otomatis = Usia - Tahun; }} tes kelas publik {public static void main (string [] args) {system.out.println (teststatic.demo.curage); Teststatic static1 = teststatic baru (15); System.out.println (static1.curage); }} Pada saat ini, usia dimodifikasi oleh final, jadi ketika menyusun, semua usia di kelas induk menjadi 20, bukan variabel, sehingga hasil output dapat memenuhi harapan kami.
Terutama saat membandingkan string, itu dapat ditampilkan lebih banyak
paket com.zlc.array; testString kelas publik {static string static_name1 = "java"; string statis static_name2 = "me"; string statis statis statci_name3 = static_name1+static_name2; string statis final final_static_name1 = "java"; string statis final final_static_name2 = "me"; // Tambahkan final atau tidak, dapat diganti dengan makro di depan. String final final_statci_name3 = final_static_name1+final_static_name2; public static void main (string [] args) {string name1 = "java"; String name2 = "me"; String name3 = name1+name2; // (1) System.out.println (name3 == "javame"); // (2) System.out.println (testString.statci_name3 == "javame"); // (3) System.out.println (testString.final_statci_name3 == "javame"); }} Tidak ada yang bisa dikatakan tentang menggunakan metode dan kelas modifikasi akhir, hanya saja yang tidak dapat ditulis ulang oleh subkelas (seperti pribadi), dan yang lain tidak dapat memperoleh subkelas.
Saat memodifikasi variabel lokal dengan final, Java mensyaratkan bahwa variabel lokal yang diakses oleh kelas internal dimodifikasi dengan final. Ada alasannya. Untuk variabel lokal biasa, ruang lingkup mereka tetap dalam metode ini. Ketika metode berakhir, variabel lokal menghilang, tetapi kelas internal dapat menghasilkan "penutupan" implisit, yang menyebabkan variabel lokal tetap terpisah dari metode di mana ia berada.
Terkadang, utas akan menjadi baru dalam suatu metode dan kemudian variabel lokal dari metode ini disebut. Pada saat ini, variabel perubahan perlu dinyatakan sebagai final dimodifikasi.
5. Metode perhitungan memori hunian objek
Gunakan metode freememory (), totalMemory (), dan maxmemory () di kelas java.lang.runtime untuk mengukur ukuran objek Java. Metode ini biasanya digunakan ketika banyak sumber daya perlu ditentukan secara tepat. Metode ini hampir tidak berguna untuk mengimplementasikan cache sistem produksi. Keuntungan dari metode ini adalah bahwa tipe data tidak tergantung pada ukuran, dan sistem operasi yang berbeda dapat memperoleh memori yang ditempati.
Ini menggunakan API refleksi untuk melintasi hierarki variabel anggota suatu objek dan menghitung ukuran semua variabel asli. Pendekatan ini tidak memerlukan begitu banyak sumber daya dan dapat digunakan untuk implementasi yang di -cache. Kerugiannya adalah bahwa ukuran jenis asli berbeda dan implementasi JVM yang berbeda memiliki metode perhitungan yang berbeda.
Setelah JDK5.0, API Instrumentasi menyediakan metode GetObjectSize untuk menghitung ukuran memori yang ditempati oleh objek.
Secara default, ukuran objek yang direferensikan tidak dihitung. Untuk menghitung objek yang dirujuk, Anda dapat menggunakan refleksi untuk mendapatkannya. Metode berikut adalah implementasi yang disediakan dalam artikel di atas yang menghitung ukuran objek referensi:
kelas publik sizeofagent {static Instrumentation Inst; / ** menginisialisasi agen*/ public static void premain (string agentArgs, Instrumentation Instp) {inst = instp; } /*** Mengembalikan ukuran objek tanpa sub-objek anggota. * @param O objek untuk mendapatkan ukuran * @return ukuran objek */public static ukuran panjang (objek o) {if (inst == null) {lempar baru ilegalstateException ("tidak dapat mengakses lingkungan instrumentasi./n" + "Harap periksa apakah file JAR yang berisi coudaGent/" n " } return inst.getObjectSize (o); } /** * Menghitung ukuran penuh objek iterasi lebih dari * grafik hierarki. * @param objek untuk menghitung ukuran * @return objek ukuran */ public static long fulsizeof (objek obj) {peta <objek, objek> visited = identityhashMap baru <objek, objek> (); Stack <BOMPERTS> stack = Stack baru <PapeT> (); Hasil Panjang = Internal (OBJ, Tumpukan, Dikunjungi); while (! stack.isempty ()) {result += internalSizeOf (stack.pop (), stack, dikunjungi); } visited.clear (); hasil pengembalian; } private static boolean skipObject (objek obj, peta <objek, objek> dikunjungi) {if (instance obj string) {// lewati string yang diinternasi if (obj == ((string) obj) .intern ()) {return true; }} return (obj == null) // lewati objek yang dikunjungi || Visited.ContainsKey (OBJ); } private static long internalSizeOf (objek obj, stack <paps> stack, peta <objek, objek> dikunjungi) {if (skipObject (obj, dikunjungi)) {return 0; } visited.put (obj, null); Hasil Panjang = 0; // Dapatkan ukuran objek + variabel primitif + hasil titik anggota + = sizeOfagent.sizeof (OBJ); // Proses semua elemen array kelas clazz = obj.getClass (); if (clazz.isArray ()) {if (clazz.getName (). length ()! = 2) {// lewati tipe primitif array int panjang = array.getLength (obj); untuk (int i = 0; i <panjang; i ++) {stack.add (array.get (obj, i)); }} hasil pengembalian; } // Memproses semua bidang objek sementara (clazz! = null) {field [] fields = clazz.getDeclaredFields (); untuk (int i = 0; i <fields.length; i ++) {if (! Modifier.isstatic (bidang [i] .getModifiers ())) {if (fields [i] .getType (). isPrimitive ())) {lanjutkan; // lewati bidang primitif} else {bidang [i] .setAccessible (true); coba {// objek yang akan diperkirakan dimasukkan ke tumpukan objek objektoadd = bidang [i] .get (obj); if (objecttoadd! = null) {stack.add (objectToadd); }} catch (ilegalAccessException ex) {Assert false; }}}}} clazz = clazz.getSuperclass (); } hasil pengembalian; }}