1、對象與內存控制的知識點 java
1.java變量的初始化過程,包括局部變量,成員變量(實例變量和類變量)。
2.繼承關係中,當使用的對象引用變量編譯時類型和運行時類型不一樣時,訪問該對象的屬性和方法是有區別的。
3.final修飾符特性。 閉包
2、java變量的劃分與初始化過程 jvm
java程序的變量大致能夠分爲成員變量和局部變量,成員變量能夠分爲實例變量(非靜態變量)和類變量(靜態變量),通常咱們遇到的局部變量會在下列幾種狀況中出現:
(1)形參:在方法簽名中定義的局部變量,由調用方爲其賦值,隨着方法結束消亡。
(2)方法內的局部變量:在方法內定義的局部變量必須在方法內顯示的初始化(賦初始值),隨着變量初始化完成開始,到方法結束而消亡。
(3)代碼塊內的局部變量:在代碼塊內定義的局部變量必須在代碼塊內顯示的初始化(賦初始值),隨着初始化完成開始生效,隨着代碼塊的結束而消亡。 ide
package com.zlc.array; public class TestField { { String b ; //若是不初始化,編譯器就會報The local variable b may not have been initialized System.out.println(b); } public static void main(String[] args) { int a ; //若是不初始化,編譯器就會報The local variable a may not have been initialized System.out.println(a); } }使用static修飾的成員變量是類變量,屬於類自己,沒有用static修飾的成員變量是實例變量,屬於該類的實例,在同一個JVM裏面,每一個類只能對應一個Class對象,但每一個類能夠建立多個java對象。(也就是說一個類變量只需一塊內存空間,而該類每建立一次實例,就須要爲實例變量分配一塊空間)
package com.zlc.array; public class TestField { public TestField(int age){ System.out.println("構造函數中初始化 this.age = "+this.age); this.age = age; } { System.out.println("非靜態塊中初始化"); age = 22; } //定義的時候初始化 int age = 15; public static void main(String[] args) { TestField field = new TestField(24); System.out.println("最終 age = "+field.age); } }運行結果爲:非靜態塊中初始化
package com.zlc.array; class TestStatic { //類成員 DEMO TestStatic實例 final static TestStatic DEMO = new TestStatic(15); //類成員 age static int age = 20; //實例變量 curAge int curAge; public TestStatic(int years) { // TODO Auto-generated constructor stub curAge = age - years; } } public class Test{ public static void main(String[] args) { System.out.println(TestStatic.DEMO.curAge); TestStatic staticDemo = new TestStatic(15); System.out.println(staticDemo.curAge); } }輸出結果有兩行打印,一個是打印TestStatic類屬性DEMO的實例變量,第二個經過java對象staticDemo輸出TestStatic的實例屬性,根據咱們上面分析的實例變量和類變量的初始化流程能夠進行推斷:
2)初始化第二階段,程序按順序依次給DEMO、age賦初始值,TestStatic(15)須要調用TestStatic的構造器,此時age = 0 因此打印結果爲 -15,而當staticDemo被初始化的時候,age已經被賦值等於20了,因此輸出結果爲5。 函數
3、在繼承關係中繼承成員變量和繼承成員方法的區別。 this
當建立任何java對象時,程序總會先調用父類的非靜態塊、父類構造器,最後才調用本類的非靜態塊和構造器。經過子類的構造器調用父類的構造器通常分爲兩種狀況,一個是隱式調用,一個經過super顯示調用父類的構造器。
子類的方法能夠調用父類的實例變量,這是由於子類繼承了父類就會獲取父類的成員變量和方法,但父類的方法不能訪問子類的實例變量,由於父類不知道它將被哪一個類繼承,它的子類將會增長什麼樣的成員變量,固然在一些極端的例子裏面仍是能夠實現父類調用子類變量的,好比:子類重寫了父類的方法,通常都會打印出默認值,由於這個時候子類的實例變量尚未初始化。
spa
package com.zlc.array; class Father{ int age = 50; public Father() { // TODO Auto-generated constructor stub System.out.println(this.getClass()); //this.sonMethod();沒法調用 info(); } public void info(){ System.out.println(age); } } public class Son extends Father{ int age = 24; public Son(int age) { // TODO Auto-generated constructor stub this.age = age; } @Override public void info() { // TODO Auto-generated method stub System.err.println(age); } public static void main(String[] args) { new Son(28); } //子類特有的方法 public void sonMethod(){ System.out.println("Son method"); } }按照咱們正常推斷,經過子類隱式的調用父類的構造器,而在父類的構造器中調用了info()方法(注意:我這裏沒有說調用父類的),按道理來講是輸出了父類的age實例變量,打印結果預計是50,但實際輸出的結果爲0,分析緣由:
3)在變量的編譯時類型和運行時類型不一樣時,經過該變量訪問它的引用對象的實例變量時,該實例變量的值由聲明該變量的類型決定,但經過該變量調用它引用的對象的實例方法時,該方法的行爲由它實際引用的對象決定,因此這裏調用的是子類的info方法,因此打印的是子類的age,因爲age還沒來得急初始化因此打印默認值0。
通俗的來講也就是,當聲明的類型和真正new的類型不一致的時候,使用的屬性是父類的,調用的方法是子類的。
經過javap -c咱們更能直接的體會爲何繼承屬性和方法會有很大的區別,若是咱們把上面例子裏面,子類Son的info重寫方法去掉,這個時候調用的會是父類的info方法,是由於在進行編譯的時候會把父類的info方法編譯轉移到子類裏面去,而聲名的成員變量會留在父類中不進行轉移,這樣子類和父類擁有了同名的實例變量,而若是子類重寫了父類的同名方法,則子類的方法會徹底覆蓋掉父類的方法(至於爲何java要這麼設計,我的也不太清楚)。同名變量能同時存在不覆蓋,同名方法子類會完全覆蓋父類同名方法。
總的來講對於一個引用變量而言,當經過該變量訪問它所引用的對象的實例變量時,該實例變量的值取決於聲明該變量時類型,當經過該變量訪問它所引用的對象的方法時,該方法行爲取決於它所實際引用的對象的類型。
最後拿個小case複習下: 線程
package com.zlc.array; class Animal{ int age ; public Animal(){ } public Animal(int age) { // TODO Auto-generated constructor stub this.age = age; } void run(){ System.out.println("animal run "+age); } } class Dog extends Animal{ int age; String name; public Dog(int age,String name) { // TODO Auto-generated constructor stub this.age = age; this.name = name; } @Override void run(){ System.out.println("dog run "+age); } } public class TestExtends { public static void main(String[] args) { Animal animal = new Animal(5); System.out.println(animal.age); animal.run(); Dog dog = new Dog(1, "xiaobai"); System.out.println(dog.age); dog.run(); Animal animal2 = new Dog(11, "wangcai"); System.out.println(animal2.age); animal2.run(); Animal animal3; animal3 = dog; System.out.println(animal3.age); animal3.run(); } }想要調用父類的方法:能夠經過super來調用,但super關鍵字沒有引用任何對象,它不能當作真正的引用變量來使用,有興趣的朋友能夠本身研究下。
4、final修飾符的使用(特別是宏替換) 設計
final能夠修飾變量,被final修飾的變量被賦初始值以後,不能對他從新賦值。
final能夠修飾方法,被final修飾的方法不能被重寫。
final能夠修飾類,被final修飾的類不能派生子類。
code
被final修飾的變量必須顯示的指定初始值:
對因而final修飾的是實例變量,則只能在下列三個指定位置賦初始值。
(1)定義final實例變量時指定初始值。
(2)在非靜態塊中爲final實例變量指定初始值。
(3)在構造器中爲final實例變量指定初始值。
最終都會被提到構造器中進行初始化。
對於用final指定的類變量:只能在指定的兩個地方進行賦初始值。
(1)定義final類變量的時候指定初始值。
(2)在靜態塊中爲final類變量指定初始值。
一樣通過編譯器處理,不一樣於實例變量的是,類變量都是提到靜態塊中進行賦初始值,而實例變量是提到構造器中完成。
被final修飾的類變量還有一種特性,就是「宏替換」,當被修飾的類變量知足在定義該變量的時候就指定初始值,並且這個初始值在編譯的時候就能肯定下來(好比:1八、"aaaa"、16.78等一些直接量),那麼該final修飾的類變量不在是一個變量,系統就會當成「宏變量」處理(就是咱們常說的常量),若是在編譯的時候就能肯定初始值,則就不會被提到靜態塊中進行初始化了,直接在類定義中直接使該初始值代替掉final變量。咱們仍是舉那個年齡減去year的例子:
package com.zlc.array; class TestStatic { //類成員 DEMO TestStatic實例 final static TestStatic DEMO = new TestStatic(15); //類成員 age final static int age = 20; //實例變量 curAge int curAge; public TestStatic(int years) { // TODO Auto-generated constructor stub curAge = age - years; } } public class Test{ public static void main(String[] args) { System.out.println(TestStatic.DEMO.curAge); TestStatic static1 = new TestStatic(15); System.out.println(static1.curAge); } }這個時候的age 被final修飾了,因此在編譯的時候,父類中全部的age都變成了20,而不是一個變量,這樣輸出的結果就能達到咱們的預期。
package com.zlc.array; public class TestString { static String static_name1 = "java"; static String static_name2 = "me"; static String statci_name3 = static_name1+static_name2; final static String final_static_name1 = "java"; final static String final_static_name2 = "me"; //加final 或者不加都行 前面兩個能被宏替換就好了 final static String 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"); } }用final修飾方法和類沒有什麼好說的,只是一個不能被子類重寫(和private同樣),一個不能派生子類。 用final修飾局部變量的時候,Java要求被內部類訪問的局部變量都是用final修飾,這個是有緣由的,對於普通局部變量而言,它的做用域就停留在該方法內,當方法結束時,該局部變量也就消失了,但內部類可能產生隱式的「閉包」,閉包使得局部變量脫離他所在的方法繼續存在。 有時候在會在一個方法裏面new 一個線程,而後調用該方法的局部變量,這個時候須要把改變量聲明爲final修飾的。