淺談Java類中的變數初始化順序
一、變數與構造器的初始化順序
我們知道一個類中具有類變數、類方法和構造器(方法中的局部變數不討論,他們是在方法調用時才被初始化),當我們初始化創建一個類對象時,其初始化的順序為:先初始化類變數,再執行構造器方法。
程式碼驗證:
public class Demo01 {
public int a1 = 1;
public String a2 = "initiaied!";
public Demo01() {
System.out.println(a1);
System.out.println(a2);
System.out.println("構造器方法被執行");
}
public static void main(String[] args) {
Demo01 demo01 = new Demo01();
}
}
運行結果:
1
initiaied!
構造器方法被執行
可以看出,當我們創建一個Demo01對象時,先初始化了變數a1和a2,然後執行構造器方法。
二、靜態變數與非靜態變數的初始化順序
靜態變數是屬於類本身,無論創建多少個對象,靜態變數都只有一份存儲區域,因此他會在類首次被訪問或者首次創建類對象時被初始化,而且有且只能初始化一次。
而非靜態變數是屬於每個對象,他是在創建每個對象時都初始化一次。因此,靜態變數要先於非靜態進行初始化。例子:
public class Demo02 {
public Cup cup1 = new Cup(1);
public static Cup cup2 = new Cup(2);
public Demo02() {
System.out.println("Demo02構造器被執行!");
}
public static void main(String[] args) {
Demo02 demo02 = new Demo02();
Demo02 demo02_01 = new Demo02();
}
}
class Cup {
public Cup(int i) {
System.out.println("Cup->" + i);
}
}
運行結果:
Cup->2
Cup->1
Demo02構造器被執行!
Cup->1
Demo02構造器被執行
當程式要執行main方法時,會先載入Demo02類文件,在載入時就會先初始化靜態變數cup2,因此控制台輸出"cup->2"。
類載入完後,開始執行main方法,創建demo02對象,這時就會初始化類中的非靜態變數cup1,控制台輸出"cup->1",然後執行構造器方法。創建第二個對象時,只初始化cup1,cup2為靜態變數只初始化一次。
三、靜態程式碼塊與非靜態程式碼塊
靜態程式碼塊本質上就是一段靜態變數的程式碼,其初始化和靜態變數沒有區別:當類首次被訪問或者首次創建該類對象時被初始化,並且只初始化一次。
非靜態程式碼塊就是一段非靜態變數的程式碼,他和非靜態變數的初始化沒有區別。
public class Demo03 {
static Table table1;
Table table2;
static {
table1 = new Table(1);
}
{
table2 = new Table(2);
}
public Demo03() {
System.out.println("Demo03構造器被執行");
}
public static void main(String[] args) {
new Demo03();
}
}
class Table {
public Table(int i) {
System.out.println("Table->" + i);
}
}
運行結果:
Table->1
Table->2
Demo03構造器被執行
四、父類與子類的初始化順序
沒有父類就沒有子類,因此無論是類載入還是創建實例對象,父類都優先於子類進行初始化。
public class Demo04 extends Insect {
private int k = fun("Demo04中的k被初始化");
private static int x2 = fun("Demo04中的x2被初始化");
public Demo04() {
System.out.println("k=" + k);
System.out.println("j=" + j);
}
public static void main(String[] args) {
Demo04 demo04 = new Demo04();
}
}
class Insect {
public int i = 9;
public int j;
public static int x1 = fun("Insect中的x1被初始化");
public Insect() {
System.out.println("i=" + i + ",j=" + j);
j = 39;
}
public static int fun(String s) {
System.out.println(s);
return 47;
}
}
運行結果:
Insect中的x1被初始化
Demo04中的x2被初始化
i=9,j=0
Demo04中的k被初始化
k=47
j=39
當執行main方法時,載入器開始載入Demo04類文件,在載入過程中,載入器會注意到他有一個父類Insect還沒被載入,因此會先載入父類Insect文件。
類載入過程中會完成靜態變數的初始化(此時並不執行構造器方法,構造器方法只有在創建對象時調用),Insect類載入完成後,接著載入Demo04類,都載入完成後,就開始執行main方法中的程式碼,創建Demo04對象。
由於繼承關係,因此先創建父類Insect的實例對象,因此父類中的變數初始化和構造器先被執行,然後在初始化子類Demo04中的非靜態變數和執行構造器方法。
五、總結
最後放一段程式碼,把前面所說情況都放在一起。
public class Son extends Father {
int m = fun("Son中的m 被初始化");
public Son() {
System.out.println("m = " + m);
System.out.println("j = " + j);
}
public static int x3 = fun("Son中的x3 被初始化");
public static void main(String[] args) {
Son son = new Son();
}
}
class Father extends GrandFather {
public int k = fun("Father中的k被初始化");
public static int x2 = fun("Father中的x2被初始化");
public Father() {
System.out.println("k=" + k);
System.out.println("j=" + j);
}
}
class GrandFather {
public int i = 9;
public int j;
public static int x1 = fun("GrandFather中的x1被初始化");
public GrandFather() {
System.out.println("i=" + i + ",j=" + j);
j = 39;
}
public static int fun(String s) {
System.out.println(s);
return 47;
}
}
運行結果:
GrandFather中的x1被初始化
Father中的x2被初始化
Son中的x3 被初始化
i=9,j=0
Father中的k被初始化
k=47
j=39
Son中的m 被初始化
m = 47
j = 39
