Java中main方法,靜態,非靜態的執行順序解析

Java程序運行時,第一件事情就是試圖訪問main方法,因爲main相等於程序的入口,如果沒有main方法,程序將無法啓動,main方法更是佔一個獨立的線程,找到main方法後,是不是就會執行mian方法塊裏的第一句話呢?答案是不一定

 看看下面兩種最常見的情況:

  第一種情況:

    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.


感謝原文作者,原文鏈接點擊打開鏈接

發佈了4 篇原創文章 · 獲贊 17 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章