該系列博文會告訴你如何從入門到進階,一步步地學習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)對子類成員數據按照它們聲明的順序初始化,執行子類構造函數的其余部分。