Java基礎系列5:Java程式碼的執行順序
- 2019 年 11 月 13 日
- 筆記
該系列博文會告訴你如何從入門到進階,一步步地學習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構造函數
由此結果可以看出:靜態程式碼塊只會在類載入的時候執行一次,而構造函數和構造程式碼塊則會在每次創建對象的都會執行一次
對於一個類而言,按照如下順序執行:
- 執行靜態程式碼塊
- 執行構造程式碼塊
- 執行構造函數
對於靜態變數、靜態初始化塊、變數、初始化塊、構造器,它們的初始化順序依次是(靜態變數、靜態初始化塊)>(變數、初始化塊)>構造器。
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)對子類成員數據按照它們聲明的順序初始化,執行子類構造函數的其餘部分。