類變量 :使用static修飾的成員變量是類變量,屬於該類自己 java
實例變量:沒有使用static修飾的成員變量是實例變量,屬於該類的實例 ide
因爲同一個JVM內每一個類只對應一個Class對象,所以同一個JVM內的一個類的類變量只需一塊內存空間。 函數
對於實例變量而言,該類每建立一次實例,就須要爲實例變量分配一塊內存空間,因此,程序中有幾個實例,實例變量就須要幾塊內存空間。 this
咱們先看下下面三段代碼: spa
1)由於兩個實例變量都是在建立變量的時候纔開始分配空間,此時num2尚未分配,因此前向引用就會出現編譯錯誤。 code
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;
由上面三段代碼塊就能夠驗證得:類變量的初始化時機老是處於實例變量的初始化以前 繼承
Java對象的初始化方式有三種:1)構造器 2)初始化塊 3)定義變量時指定初始化值 內存
若是這三種初始化方式同時出現,也要注意,他們也有一個執行順序的規定:
1)靜態初始化塊只在類第一次建立對象的時候運行一次,後面就不會再運行,而類在每次建立對象時,非靜態初始化塊老是會運行一次。
public class Test{ static { System.out.println("執行---靜態初始化代碼塊."); } { System.out.println("執行---非靜態初始化代碼塊."); } public static void main(String[] args) { for (int i = 1; i <= 2; i++) { System.out.println("建立第 " + i + " 個對象"); new Test(); System.out.println(); } } }
運行結果:
2)構造器每次建立對象時,構造器必然有執行的機會,此時,非靜態初始化塊一定也將得到機會而且運行在構造器以前
public class Test{ { System.out.println("執行---非靜態初始化代碼塊."); } public Test() { System.out.println("執行---構造器."); } public static void main(String[] args) { for (int i = 1; i <= 2; i++) { System.out.println("建立第 " + i + " 個對象"); new Test(); System.out.println(); } } }
運行結果:
3)定義變量時指定的初始化值和初始化塊中指定的初始值的執行順序與他們在源程序中的排列順序相同。
驗證代碼一:
public class Test{ String i = "定義變量時指定的初始化值"; { i = "初始化塊中指定的初始值"; } public static void main(String[] args) { for (int i = 1; i <= 2; i++) { System.out.println("建立第 " + i + " 個對象"); System.out.println(new Test().i); System.out.println(); } } }
運行結果
驗證代碼二 :
public class Test{ { i = "初始化塊中指定的初始值"; } String i = "定義變量時指定的初始化值"; public static void main(String[] args) { for (int i = 1; i <= 2; i++) { System.out.println("建立第 " + i + " 個對象"); System.out.println(new Test().i); System.out.println(); } } }
運行結果:
(通常狀況下是不用內部類來驗證的,可是都是同樣的啦,我偷懶下,因此使用了內部類,你們原諒哈)
1)當子類重寫父類方法後,父類表面上只是調用屬於本身的被子類重寫的方法。
public class Test{ class Base { Base() { this.info(); } public void info() { System.out.println("Base"); } public void getInfo() { info(); } } public class Child extends Base{ @Override public void info() { System.out.println("Child"); } } public static void main(String[] args) { Test test = new Test(); Base base = test.new Child(); base.info(); base.getInfo(); } }
運行結果:
2)上述是屬於多態中方法的體現,可是方法有多態,實例變量無多態。
解釋下「方法有多態,變量無多態」這句話:意思是,無論怎樣,父類表面上只是調用屬於本身的被子類重寫的方法。而變量不同,假設父類和子類都有同一個變量名的實例變量,向上轉型後,經過父類訪問的實例變量獲得的值是自身的而非子類的。向下轉型後,經過子類訪問的實例變量獲得的值是自身的而非父類的。
不少書上或教學視頻上都講,建立一個子類對象的時候,Java 會順着繼承結構往上一直找到 Object,而後從 Object 開始往下依次執行構造函數。先執行父類的構造函數,而後在其子類中會建立一個成員變量指向他的父類。其實這個說法是錯誤的,系統並不會真正的去建立父類對象,只是在子類對象中不只保存了自己的實例變量,還有它父類的所有實例變量。
public class Test{ class Base { //父類 int i = 2; } public class Child extends Base{ //子類 int i = 20; } public static void main(String[] args) { Test test = new Test(); Child child = test.new Child(); Base base = child; System.out.println(" Base.i : " + base.i); System.out.println("Child.i : " + child.i); } }
運行結果:
final變量在編譯時就被肯定下來了,至關於一個直接量。
1)final修飾的實例變量賦值時機:
2)final修飾的類變量賦值時機: