看看下面兩種最常見的情況:
第一種情況:
main方法在一個具有其他方法或屬性的類中;
public class Test1 {
public static String name;
public Test1() {
}
public static void get() {
System.out.println("Test start");
}
public static void main(String[] args) {
System.out.println("main start");
Test1 bb = new Test1();
}
}
第二種情況:
main方法在一個沒有其他方法或屬性的類中;
public class Test {
public static void main(String[] args) {
Student stu =new Student();
}
}
分析:
因爲靜態部分是依賴於類,而不是依賴於對象存在的,所以靜態部分的加載優先於對象存在。
當找到main方法後,因爲main方法雖然是一個特殊的靜態方法,但是還是靜態方法,此時JVM會加載main方法所在的類,試圖找到類中其他靜態部分,即首先會找main方法所在的類。
執行順序大致分類:
1.靜態屬性,靜態方法聲明,靜態塊。
2.動態屬性,普通方法聲明,構造塊。
3.構造方法。
1.1 靜態:
當加載一個類時,JVM會根據屬性的數據類型第一時間賦默認值(一舉生成的)。然後再進行靜態屬性初始化,併爲靜態屬性分配內存空間,靜態方法的聲明,靜態塊的加載,沒有優先級之分,按出現順序執行,靜態部分僅僅加載一次。至此爲止,必要的類都已經加載完畢,對象就可以被創建了。
1.2 普通:
當new一個對象時,此時會調用構造方法,但是在調用構造方法之前,(此刻1.1已經完成,除非被打斷而暫停)執行動態屬性定義並設置默認值(一舉生成的)。然後動態屬性初始化,分配內存,構造塊,普通方法聲明(只是加載,它不需要初始化,只有調用它時才分配內存,當方法執行完畢後內存立即釋放),沒有優先級之分,按出現順序執行。最後進行構造方法中賦值。當再次創建一個對象,不再執行靜態部分,僅僅重複執行普通部分。
注意:如果存在繼承關係,創建對象時,依然會首先進行動態屬性進行定義並設默認值,然後父類的構造器纔會被調用,其他一切都是先父類再子類(因爲子類的static初始化可能會依賴於父類成員能否被正確初始化),如果父類還有父類,依次類推,不管你是否打算產生一個該父類的對象,這都是自然發生的。
下面是一道小程序:
代碼部分:
class A {
public A() {
System.out.println("A的構造方法");
}
public static int j = print();
public static int print() {
System.out.println("A print");
return 521;
}
}
public class Test1 extends A {
public Test1() {
System.out.println("Test1的構造方法");
}
public static int k = print();
public static int print() {
System.out.println("Test print");
return 522;
}
public static void main(String[] args) {
System.out.println("main start");
Test1 t1 = new Test1();
}
}
運行結果:A print
Test print
main start
A的構造方法
Test1的構造方法
下面是一道阿里巴巴的面試題:非常經典,可以不斷的改變程序的順序,來找到執行順序機制。
代碼部分:
public class Text {
public static int k = 0;
public static Text t1 = new Text("t1");
public static Text t2 = new Text("t2");
public static int i = print("i");
public static int n = 99;
public int j = print("j");
{
print("構造塊");
}
static {
print("靜態塊");
}
public Text(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++i;
++n;
}
public static int print(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++n;
return ++i;
}
public static void main(String args[]) {
Text t = new Text("init");
}
}
運行結果:
1:j i=0 n=0 2:構造塊 i=1 n=1 3:t1 i=2 n=2 4:j i=3 n=3 5:構造塊 i=4 n=4 6:t2 i=5 n=5 7:i i=6 n=6 8:靜態塊 i=7 n=99 9:j i=8 n=100 10:構造塊 i=9 n=101 11:init i=10 n=102
總結:只要按照這個步驟,遇到這一類問題就可以解決了。
1-3:類加載過程,不涉及構造方法
1-5: 實例化過程,涉及構造方法
1.類中所有屬性的默認值(一舉而成)
2. 父類靜態屬性初始化,靜態塊,靜態方法的聲明(按出現順序執行)
3. 子類靜態屬性初始化,靜態塊,靜態方法的聲明 (按出現順序執行)
4. 調用父類的構造方法,
首先父類的非靜態成員初始化,構造塊,普通方法的聲明(按出現順序執行)
然後父類構造方法
5. 調用子類的構造方法,
首先子類的非靜態成員初始化,構造塊,普通方法的聲明(按出現順序執行)
然後子類構造方法
(注意:類加載過程中,可能調用了實例化過程(因爲static可以修飾方法,屬性,代碼塊,內部類),此時則會暫停類加載過程而先執行實例化過程(被打斷),執行結束再進行類加載過程,上面阿里那道面試題就是典型的暫停類加載。
下面附加一個小程序來顯示新建對象時屬性的四個過程:
public class Test { public static void main(String[] args) { Student stu = new Student("zhangan", 21); } } class Student { public String name = "李尋歡"; public int age = 20; public Student(String name, int age) { this.name = name; this.age = age; } }
1.
2.
3.
4.
感謝原文作者,原文鏈接點擊打開鏈接