1.實例變量和類變量的內存分配
類變量 :使用static修飾的成員變量是類變量,屬於該類本身
實例變量:沒有使用static修飾的成員變量是實例變量,屬於該類的實例
由於同一個JVM內每個類只對應一個Class對象,因此同一個JVM內的一個類的類變量只需一塊內存空間。
對於實例變量而言,該類每創建一次實例,就需要為實例變量分配一塊內存空間,所以,程序中有幾個實例,實例變量就需要幾塊內存空間。
2.類變量的初始化時機總是處於實例變量的初始化之前
我們先看下下面三段代碼:
1)因為兩個實例變量都是在創建變量的時候才開始分配空間,此時num2還沒有分配,所以前向引用就會出現編譯錯誤。
1 int num = num2 + 3; //非法前向引用,會報錯 2 int num2 = 2
2)因為兩個類變量在JVM加載類的時候分配空間,此時num2還沒有分配,所以前向引用就出現編譯錯誤。
1 static int num = num2 + 3; //非法前向引用,會報錯 2 static int num2 = 2
3)因為類變量num2在JVM加載類的時候空間已經分配好,而num在創建實例的時候才分配空間,此時num2已經分配成功了,所以num前向引用成功。
1 int num = num2 + 3; //正確使用 2 static int num2 = 2;
由上面三段代碼塊就可以驗證得:類變量的初始化時機總是處於實例變量的初始化之前
3.Java對象的初始化方式及其執行順序
Java對象的初始化方式有三種:1)構造器 2)初始化塊 3)定義變量時指定初始化值
如果這三種初始化方式同時出現,也要注意,他們也有一個執行順序的規定:
1)靜態初始化塊只在類第一次創建對象的時候運行一次,后面就不會再運行,而類在每次創建對象時,非靜態初始化塊總是會運行一次。
1 public class Test{ 2 static { 3 System.out.println("執行---靜態初始化代碼塊."); 4 } 5 6 { 7 System.out.println("執行---非靜態初始化代碼塊."); 8 } 9 10 public static void main(String[] args) { 11 for (int i = 1; i <= 2; i++) { 12 System.out.println("創建第 " + i + " 個對象"); 13 new Test(); 14 System.out.println(); 15 } 16 } 17 }
運行結果:
2)構造器每次創建對象時,構造器必然有執行的機會,此時,非靜態初始化塊必定也將獲得機會並且運行在構造器之前
1 public class Test{ 2 3 { 4 System.out.println("執行---非靜態初始化代碼塊."); 5 } 6 7 public Test() { 8 System.out.println("執行---構造器."); 9 } 10 11 public static void main(String[] args) { 12 for (int i = 1; i <= 2; i++) { 13 System.out.println("創建第 " + i + " 個對象"); 14 new Test(); 15 System.out.println(); 16 } 17 } 18 }
運行結果:
3)定義變量時指定的初始化值和初始化塊中指定的初始值的執行順序與他們在源程序中的排列順序相同。
驗證代碼一:
1 public class Test{ 2 3 String i = "定義變量時指定的初始化值"; 4 5 { 6 i = "初始化塊中指定的初始值"; 7 } 8 9 public static void main(String[] args) { 10 for (int i = 1; i <= 2; i++) { 11 System.out.println("創建第 " + i + " 個對象"); 12 System.out.println(new Test().i); 13 System.out.println(); 14 } 15 } 16 }
運行結果
驗證代碼二 :
1 public class Test{ 2 3 { 4 i = "初始化塊中指定的初始值"; 5 } 6 7 String i = "定義變量時指定的初始化值"; 8 9 public static void main(String[] args) { 10 for (int i = 1; i <= 2; i++) { 11 System.out.println("創建第 " + i + " 個對象"); 12 System.out.println(new Test().i); 13 System.out.println(); 14 } 15 } 16 }
運行結果:
4.關於父子實例的內存控制
(一般情況下是不用內部類來驗證的,但是都是一樣的啦,我偷懶下,所以使用了內部類,大家原諒哈)
1)當子類重寫父類方法后,父類表面上只是調用屬於自己的被子類重寫的方法。
1 public class Test{ 2 class Base { 3 Base() { 4 this.info(); 5 } 6 public void info() { 7 System.out.println("Base"); 8 } 9 public void getInfo() { 10 info(); 11 } 12 } 13 14 public class Child extends Base{ 15 @Override 16 public void info() { 17 System.out.println("Child"); 18 } 19 } 20 21 public static void main(String[] args) { 22 Test test = new Test(); 23 Base base = test.new Child(); 24 base.info(); 25 base.getInfo(); 26 } 27 }
運行結果:
2)上述是屬於多態中方法的體現,但是方法有多態,實例變量無多態。
解釋下“方法有多態,變量無多態”這句話:意思是,不管怎樣,父類表面上只是調用屬於自己的被子類重寫的方法。而變量不一樣,假設父類和子類都有同一個變量名的實例變量,向上轉型后,通過父類訪問的實例變量得到的值是自身的而非子類的。向下轉型后,通過子類訪問的實例變量得到的值是自身的而非父類的。
很多書上或教學視頻上都講,創建一個子類對象的時候,Java 會順着繼承結構往上一直找到 Object,然后從 Object 開始往下依次執行構造函數。先執行父類的構造函數,然后在其子類中會創建一個成員變量指向他的父類。其實這個說法是錯誤的,系統並不會真正的去創建父類對象,只是在子類對象中不僅保存了本身的實例變量,還有它父類的全部實例變量。
1 public class Test{ 2 class Base { //父類 3 int i = 2; 4 } 5 6 public class Child extends Base{ //子類 7 int i = 20; 8 } 9 10 public static void main(String[] args) { 11 Test test = new Test(); 12 Child child = test.new Child(); 13 Base base = child; 14 System.out.println(" Base.i : " + base.i); 15 System.out.println("Child.i : " + child.i); 16 } 17 }
運行結果:
5.final修飾符
final變量在編譯時就被確定下來了,相當於一個直接量。
1)final修飾的實例變量賦值時機:
- 定義final實例變量時 指定初始值
- 在非靜態初始化模塊中為final實例變量指定的初始值
- 在構造器中為final實例變量指定初始值
2)final修飾的類變量賦值時機:
- 定義final類變量時指定初始值
- 在靜態初始化模塊中為final實例變量指定的初始值