1. 程式人生 > >Java基礎系列5:Java程式碼的執行順序

Java基礎系列5:Java程式碼的執行順序

該系列博文會告訴你如何從入門到進階,一步步地學習Java基礎知識,並上手進行實戰,接著瞭解每個Java知識點背後的實現原理,更完整地瞭解整個Java技術體系,形成自己的知識框架。

 

一、構造方法

構造方法(或建構函式)是類的一種特殊方法,用來初始化類的一個新的物件。Java 中的每個類都有一個預設的構造方法,它必須具有和類名相同的名稱,而且沒有返回型別。構造方法的預設返回型別就是物件型別本身,並且構造方法不能被 static、final、synchronized、abstract 和 native 修飾。

提示:構造方法用於初始化一個新物件,所以用 static 修飾沒有意義;構造方法不能被子類繼承,所以用 final 和 abstract 修飾沒有意義;多個執行緒不會同時建立記憶體地址相同的同一個物件,所以用 synchronized 修飾沒有必要。

構造方法的語法格式如下:

public class Person {
	
	/**
	 * 1.構造方法沒有返回值 預設返回型別就是物件型別本身
	 * 2.構造方法的方法名和類名相同
	 */
	
	//無參構造方法
	public Person() {
		System.out.println("我是無參構造方法");
	}
	
	//有參構造方法
	public Person(String username,Integer age) {
		System.out.println("我是有參構造"+"姓名:"+username+"  密碼:"+age);
	}
	
	public static void main(String[] args) {
		Person p1=new Person();//呼叫無參構造
		
		Person p2=new Person("小王",12);//呼叫有參構造
	}

}

  

關於構造方法,需要注意:

  • 如何呼叫:
    • 構造方法在例項化的時候呼叫,如上述程式碼中的Person p1=new Person(),這裡便呼叫了Person類的無參構造,構造方法由系統自動呼叫
  • 建構函式過載
    • 我們知道方法可以過載(方法名相同,引數列表不同),那麼構造方法也是方法的一種,當然也可以繼承,如上述程式碼中的兩個構造方法,一個無參構造方法,一個帶兩個引數的構造方法。
    • 當有多個構造方法時,程式會在你建立類時根據你傳入的引數決定呼叫哪個構造方法
  • 預設構造方法
    • 細心的讀者可能會有疑問,之前建立類的時候我並沒有宣告建構函式,但是也可以建立類,是不是可以說類不需要建構函式也可以建立。不是滴,當你沒有顯示宣告建構函式時,程式會自動生成一個預設的無參建構函式
    • 並且該建構函式的許可權是隨著類的改變而改變的(類為public,建構函式也為public;類改為private,建構函式也改為private);而當該類一旦聲明瞭建構函式以後,java 是不會再給該類分配預設的建構函式。就是說,一旦你聲明瞭建構函式,並且該建構函式有形參,那麼你就不能pen ipen=new pen();像這樣宣告一個物件了。
  • 構造方法作用:
    • 建構函式是用於物件初始化
    • 一個物件建立,建構函式只執行一次,而一般方法可以被該物件呼叫多次。

 

二、程式碼塊

1、普通程式碼塊:

普通程式碼塊是我們用得最多的也是最普遍的,它就是在方法名後面用{}括起來的程式碼段。普通程式碼塊是不能夠單獨存在的,它必須要緊跟在方法名後面。同時也必須要使用方法名呼叫它。

public class Test {
    public void test(){
        System.out.println("普通程式碼塊");
    }
}

  

2、構造程式碼塊:

在類中直接定義沒有任何修飾符、字首、字尾的程式碼塊即為構造程式碼塊。我們明白一個類必須至少有一個建構函式,建構函式在生成物件時被呼叫。構造程式碼塊和建構函式一樣同樣是在生成一個物件時被呼叫

public class Test{
  {
      System.out.println("我是構造程式碼塊");
  }
}    

 

注意:

  • 構造程式碼塊的作用是給物件初始化。
  • 物件一建立就呼叫構造程式碼塊了,而且優於建構函式執行。這裡強調一下,有物件建立,才會執行構造程式碼塊,類不能呼叫構造程式碼塊的,而且構造程式碼塊與建構函式的執行順序是前者先於後者執行。
  • 構造程式碼塊與建構函式的區別是:構造程式碼塊是給所有物件進行統一初始化,而建構函式是給對應的物件初始化,因為建構函式是可以多個的,執行哪個建構函式就會建立什麼樣的物件,但無論建立哪個物件,都會先執行相同的構造程式碼塊。也就是說,構造程式碼塊中定義的是不同物件共性的初始化內容。

  

 

3、靜態程式碼塊:

想到靜態我們就會想到static,靜態程式碼塊就是用static修飾的用{}括起來的程式碼段,它的主要目的就是對靜態屬性進行初始化。

public class Test {
    static{
        System.out.println("靜態程式碼塊");
    }
}

  

注意:

  • 靜態程式碼塊隨著類的載入而執行,而且只會執行一次,並優於主函式。具體說靜態程式碼塊由類呼叫,類呼叫時先執行靜態程式碼塊,然後才執行主函式。
  • 靜態程式碼塊是給類初始化的,而構造程式碼塊是給物件初始化的。
  • 靜態程式碼塊中的變數是區域性變數,和普通方法中的區域性變數沒有區別。
  • 一個類中可以有多個靜態程式碼塊。

 

三、Java類的初始化順序

1、一個類的情況:

A:

public class Test {
	
	public Test(){
		System.out.println("Test建構函式");
	}
	
	{
		System.out.println("Test構造程式碼塊");
	}
	
	static {
		System.out.println("靜態程式碼塊");
	}
	
	
	public static void main(String[] args) {
		
	}

}

  

結果:

靜態程式碼塊

  

B:

public class Test {
	
	public Test(){
		System.out.println("Test建構函式");
	}
	
	{
		System.out.println("Test構造程式碼塊");
	}
	
	static {
		System.out.println("靜態程式碼塊");
	}
	
	
	public static void main(String[] args) {
		Test t=new Test();//建立了一個物件
		
	}

}

  

這段程式碼相比於上述程式碼多了一個建立物件的程式碼

結果:

靜態程式碼塊
Test構造程式碼塊
Test建構函式

  

C:

public class Test {
	
	public Test(){
		System.out.println("Test建構函式");
	}
	
	{
		System.out.println("Test構造程式碼塊");
	}
	
	static {
		System.out.println("靜態程式碼塊");
	}
	
	
	public static void main(String[] args) {
		Test t1=new Test();//建立了一個物件
		
		Test t2=new Test();
		
	}

}

  

結果:

靜態程式碼塊
Test構造程式碼塊
Test建構函式
Test構造程式碼塊
Test建構函式

  

由此結果可以看出:靜態程式碼塊只會在類載入的時候執行一次,而建構函式和構造程式碼塊則會在每次建立物件的都會執行一次

 

對於一個類而言,按照如下順序執行:

  1. 執行靜態程式碼塊
  2. 執行構造程式碼塊
  3. 執行建構函式

對於靜態變數、靜態初始化塊、變數、初始化塊、構造器,它們的初始化順序依次是(靜態變數、靜態初始化塊)>(變數、初始化塊)>構造器。

 

D:

public class Test {
	
	//靜態變數
	public static String staticField="靜態變數";
	
	//變數
	public String field="變數";
	
	//靜態初始化塊
	static {
		System.out.println(staticField);
		System.out.println("靜態初始化塊");
	}
	
	{
		System.out.println(field);
		System.out.println("初始化塊");
	}
	
	//建構函式
	public Test() {
		System.out.println("建構函式");
	}
	
	public static void main(String[] args) {
		Test t=new Test();
	}

}

  

結果:

靜態變數
靜態初始化塊
變數
初始化塊
建構函式

  

2、繼承情況下的程式碼執行順序:

class TestA{
	public TestA() {
		System.out.println("A的建構函式");
	}
	
	{
		System.out.println("A的構造程式碼塊");
	}
	
	static {
		System.out.println("A的靜態程式碼塊");
	}
}

public class TestB extends TestA {
	
	public TestB() {
		System.out.println("B的建構函式");
	}
	
	{
		System.out.println("B的構造程式碼塊");
	}
	
	static {
		System.out.println("B的靜態程式碼塊");
	}

	public static void main(String[] args) {
		TestB t=new TestB();
	}
	
}

  

這裡有兩個類,屬於繼承的關係,讀者先不要看答案,自己思考一下結果是啥?

1 A的靜態程式碼塊
2 B的靜態程式碼塊
3 A的構造程式碼塊
4 A的建構函式
5 B的構造程式碼塊
6 B的建構函式
結果

 

 

當設計到繼承時,程式碼的執行順序如下:

1、執行父類的靜態程式碼塊,並初始化父類的靜態成員

2、執行子類的靜態程式碼塊,並初始化子類的靜態成員

3、執行父類的構造程式碼塊,執行父類的建構函式,並初始化父類的普通成員變數

4、執行子類的構造程式碼塊,執行子類的建構函式,並初始化子類的普通成員變數

 

Java初始化流程圖:

 

 

 

class Parent {
	/* 靜態變數 */
	public static String p_StaticField = "父類--靜態變數";
	/* 變數 */
	public String p_Field = "父類--變數";
	protected int i = 9;
	protected int j = 0;
	/* 靜態初始化塊 */
	static {
		System.out.println(p_StaticField);
		System.out.println("父類--靜態初始化塊");
	}
	/* 初始化塊 */
	{
		System.out.println(p_Field);
		System.out.println("父類--初始化塊");
	}

	/* 構造器 */
	public Parent() {
		System.out.println("父類--構造器");
		System.out.println("i=" + i + ", j=" + j);
		j = 20;
	}
}

public class SubClass extends Parent {
	/* 靜態變數 */
	public static String s_StaticField = "子類--靜態變數";
	/* 變數 */
	public String s_Field = "子類--變數";
	/* 靜態初始化塊 */
	static {
		System.out.println(s_StaticField);
		System.out.println("子類--靜態初始化塊");
	}
	/* 初始化塊 */
	{
		System.out.println(s_Field);
		System.out.println("子類--初始化塊");
	}

	/* 構造器 */
	public SubClass() {
		System.out.println("子類--構造器");
		System.out.println("i=" + i + ",j=" + j);
	}

	/* 程式入口 */
	public static void main(String[] args) {
		System.out.println("子類main方法");
		new SubClass();
	}
}

  

結果:

父類--靜態變數
父類--靜態初始化塊
子類--靜態變數
子類--靜態初始化塊
子類main方法
父類--變數
父類--初始化塊
父類--構造器
i=9, j=0
子類--變數
子類--初始化塊
子類--構造器
i=9,j=20

  

(1)訪問SubClass.main(),(這是一個static方法),於是裝載器就會為你尋找已經編譯的SubClass類的程式碼(也就是SubClass.class檔案)。在裝載的過程中,裝載器注意到它有一個基類(也就是extends所要表示的意思),於是它再裝載基類。不管你創不建立基類物件,這個過程總會發生。如果基類還有基類,那麼第二個基類也會被裝載,依此類推。

(2)執行根基類的static初始化,然後是下一個派生類的static初始化,依此類推。這個順序非常重要,因為派生類的“static初始化”有可能要依賴基類成員的正確初始化。

(3)當所有必要的類都已經裝載結束,開始執行main()方法體,並用new SubClass()建立物件。

(4)類SubClass存在父類,則呼叫父類的建構函式,你可以使用super來指定呼叫哪個建構函式。基類的構造過程以及構造順序,同派生類的相同。首先基類中各個變數按照字面順序進行初始化,然後執行基類的建構函式的其餘部分。

(5)對子類成員資料按照它們宣告的順序初始化,執行子類建構函式的其餘部分。 

&n