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)對子類成員數據按照它們聲明的順序初始化,執行子類構造函數的其餘部分。 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?