一、變數與構造器的初始化順序
我們知道一個類中具有類變數、類方法和構造器(方法中的區域性變數不討論,他們是在方法呼叫時才被初始化),當我們初始化建立一個類物件時,其初始化的順序為:先初始化類變數,再執行構造器方法。
程式碼驗證:
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