夯實Java基礎系列7:一文讀懂Java 程式碼塊和執行順序
- 2019 年 10 月 8 日
- 筆記
目錄
- Java中的構造方法
- 構造方法簡介
- 構造方法實例
- 例 1
- 例 2
- Java中的幾種構造方法詳解
- 普通構造方法
- 默認構造方法
- 重載構造方法
- java子類構造方法調用父類構造方法
- Java中的程式碼塊簡介
- Java程式碼塊使用
- 局部程式碼塊
- 構造程式碼塊
- 靜態程式碼塊
- Java程式碼塊、構造方法(包含繼承關係)的執行順序
- 參考文章
- 微信公眾號
- Java技術江湖
- 個人公眾號:黃小斜
本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到我的倉庫里查看
喜歡的話麻煩點下Star哈
文章首發於我的個人部落格:
www.how2playlife.com
本文是微信公眾號【Java技術江湖】的《夯實Java基礎系列博文》其中一篇,本文部分內容來源於網路,為了把本文主題講得清晰透徹,也整合了很多我認為不錯的技術部落格內容,引用其中了一些比較好的部落格文章,如有侵權,請聯繫作者。
該系列博文會告訴你如何從入門到進階,一步步地學習Java基礎知識,並上手進行實戰,接著了解每個Java知識點背後的實現原理,更完整地了解整個Java技術體系,形成自己的知識框架。為了更好地總結和檢驗你的學習成果,本系列文章也會提供每個知識點對應的面試題以及參考答案。
如果對本系列文章有什麼建議,或者是有什麼疑問的話,也可以關注公眾號【Java技術江湖】聯繫作者,歡迎你參與本系列博文的創作和修訂。
<!– more –>
Java中的構造方法
構造方法簡介
構造方法是類的一種特殊方法,用來初始化類的一個新的對象。Java 中的每個類都有一個默認的構造方法,它必須具有和類名相同的名稱,而且沒有返回類型。構造方法的默認返回類型就是對象類型本身,並且構造方法不能被 static、final、synchronized、abstract 和 native 修飾。
提示:構造方法用於初始化一個新對象,所以用 static 修飾沒有意義;構造方法不能被子類繼承,所以用 final 和 abstract 修飾沒有意義;多個執行緒不會同時創建記憶體地址相同的同一個對象,所以用 synchronized 修飾沒有必要。
構造方法的語法格式如下:
<pre>class class_name
{
public class_name(){} //默認無參構造方法
public ciass_name([paramList]){} //定義構造方法
…
//類主體
}</pre>
在一個類中,與類名相同的方法就是構造方法。每個類可以具有多個構造方法,但要求它們各自包含不同的方法參數。
構造方法實例
例 1
構造方法主要有無參構造方法和有參構造方法兩種,示例如下:
<pre>public class MyClass
{
private int m; //定義私有變數
MyClass()
{
//定義無參的構造方法
m=0;
}
MyCiass(int m)
{
//定義有參的構造方法
this.m=m;
}
}</pre>
該示例定義了兩個構造方法,分別是無參構造方法和有參構造方法。在一個類中定義多個具有不同參數的同名方法,這就是方法的重載。這兩個構造方法的名稱都與類名相同,均為 MyClass。在實例化該類時可以調用不同的構造方法進行初始化。
注意:類的構造方法不是要求必須定義的。如果在類中沒有定義任何一個構造方法,則 Java 會自動為該類生成一個默認的構造方法。默認的構造方法不包含任何參數,並且方法體為空。如果類中顯式地定義了一個或多個構造方法,則 Java 不再提供默認構造方法。
例 2
要在不同的條件下使用不同的初始化行為創建類的對象,這時候就需要在一個類中創建多個構造方法。下面通過一個示例來演示構造方法的使用。
(1) 首先在員工類 Worker 中定義兩個構造方法,程式碼如下:
<pre>public class Worker
{
public String name; //姓名
private int age; //年齡
//定義帶有一個參數的構造方法
public Worker(String name)
{
this.name=name;
}
//定義帶有兩個參數的構造方法
public Worker(String name,int age)
{
this.name=name;
this.age=age;
}
public String toString()
{
return"大家好!我是新來的員工,我叫"+name+",今年"+age+"歲。";
}
}</pre>
在 Worker 類中定義了兩個屬性,其中 name 屬性不可改變。分別定義了帶有一個參數和帶有兩個參數的構造方法,並對其屬性進行初始化。最後定義了該類的 toString() 方法,返回一條新進員工的介紹語句。
提示:Object 類具有一個 toString() 方法,該方法是個特殊的方法,創建的每個類都會繼承該方法,它返回一個 String 類型的字元串。如果一個類中定義了該方法,則在調用該類對象時,將會自動調用該類對象的 toString() 方法返回一個字元串,然後使用「System.out.println(對象名)」就可以將返回的字元串內容列印出來。
(2) 在 TestWorker 類中創建 main() 方法作為程式的入口處,在 main() 方法中調用不同的構造方法實例化 Worker 對象,並對該對象中的屬性進行初始化,程式碼如下:
<pre>public class TestWorker
{
public static void main(String[] args)
{
System.out.println("-----------帶有一個參數的構造方法-----------");
//調用帶有一個參數的構造方法,Staff類中的sex和age屬性值不變
Worker worker1=new Worker("張強");
System.out.println(worker1);
System.out.println("-----------帶有兩個參數的構造方法------------");
//調用帶有兩個參數的構造方法,Staff類中的sex屬性值不變
Worker worker2=new Worker("李麗",25);
System.out.println(worker2);
}
}</pre>
在上述程式碼中,創建了兩個不同的 Worker 對象:一個是姓名為張強的員工對象,一個是姓名為李麗、年齡為 25 的員工對象。對於第一個 Worker 對象 Worker1,並未指定 age 屬性值,因此程式會將其值採用默認值 0。對於第二個 Worker 對象 Worker2,分別對其指定了 name 屬性值和 age 屬性值,因此程式會將傳遞的參數值重新賦值給 Worker 類中的屬性值。
運行 TestWorker 類,輸出的結果如下:
<pre>———–帶有一個參數的構造方法———–
大家好!我是新來的員工,我叫張強,今年0歲。
———–帶有兩個參數的構造方法————
大家好!我是新來的員工,我叫李麗,今年25歲。</pre>
通過調用帶參數的構造方法,在創建對象時,一併完成了對象成員的初始化工作,簡化了對象初始化的程式碼。
Java中的幾種構造方法詳解
普通構造方法
方法名與類名相同
無返回類型
子類不能繼承父類的構造方法
不能被static、final、abstract修飾(有final和static修飾的是不能被子類繼承的,abstract修飾的是抽象類,抽象類是不能實例化的,也就是不能new)
可以被private修飾,可以在本類裡面實例化,但是外部不能實例化對象(注意!!!)
public class A{ int i=0; public A(){ i=2; } public A(int i){ this.i=i; } }
默認構造方法
如果沒有任何的構造方法,編譯時系統會自動添加一個默認無參構造方法
隱含的默認構造方法
public A(){}
顯示的默認構造方法
public A(){ System.out.print("顯示的默認構造方法") }
重載構造方法
比如原本的類里的構造方法是一個參數的,現在新建的對象是有三個參數,此時就要重載構造方法
當一個類中有多個構造方法,有可能會出現重複性操作,這時可以用this語句調用其他的構造方法。
public class A{ private int age; private String name; public A(int age,String name){ this.age=age; this.name=name; } public A(int age){ this(age,"無名氏");//調用 A(int age,String name)構造方法 } public A(){ this(1);//調用 A(int age)構造方法 } public void setName(String name) {this.name=name;} public String getName() {return name;} public void setAge(int age) {this.age=age;} public int getAge() {return age;} } A a=new A(20,"周一"); A b=new A(20); A c=new A(); String name = a.getName(); String name1 = b.getName(); int age = c.getAge(); System.out.println(name); System.out.println(name1); System.out.println(age);
java子類構造方法調用父類構造方法
首先父類構造方法是絕對不能被子類繼承的。
子類構造方法調用父類的構造方法重點是:子類構造方法無論如何都要調用父類的構造方法。
子類構造方法要麼調用父類無參構造方法(包括當父類沒有構造方法時。系統默認給的無參構造方法),要麼調用父類有參構造方法。當子類構造方法調用父類無參構造方法,一般都是默認不寫的,要寫的話就是super(),且要放在構造方法的第一句。當子類構造方法要調用父類有參數的構造方法,那麼子類的構造方法中必須要用super(參數)調用父類構造方法,且要放在構造方法的第一句。
當子類的構造方法是無參構造方法時,必須調用父類無參構造方法。因為系統會自動找父類有沒有無參構造方法,如果沒有的話系統會報錯:說父類沒有定義無參構造方法。
當子類構造方法是有參構造方法時,這時就會有兩種情況。
第一種:子類構造方法沒有寫super,也就是說你默認調用父類無參構造方法,這樣的話就和子類是無參構造方法一樣。
第二種:子類構造方法有super(參數)時,就是調用父類有參構造方法,系統會找父類有沒有參數一致(參數數量,且類型順序要相同)的有參構造方法,如果沒有的話,同樣也會報錯。
但是這裡會遇到和重載構造方法this一樣問題,一個參數的構造方法可以調用多個參數構造方法,沒有的參數給一個自己定義值也是可以的。
Java中的程式碼塊簡介
在java中用{}括起來的稱為程式碼塊,程式碼塊可分為以下四種:
一.簡介
1.普通程式碼塊:
類中方法的方法體
2.構造程式碼塊:
構造塊會在創建對象時被調用,每次創建時都會被調用,優先於類構造函數執行。
3.靜態程式碼塊:
用static{}包裹起來的程式碼片段,只會執行一次。靜態程式碼塊優先於構造塊執行。
4.同步程式碼塊:
使用synchronized(){}包裹起來的程式碼塊,在多執行緒環境下,對共享數據的讀寫操作是需要互斥進行的,否則會導致數據的不一致性。同步程式碼塊需要寫在方法中。
二.靜態程式碼塊和構造程式碼塊的異同點
相同點:都是JVM載入類後且在構造函數執行之前執行,在類中可定義多個,一般在程式碼塊中對一些static變數進行賦值。
不同點:靜態程式碼塊在非靜態程式碼塊之前執行。靜態程式碼塊只在第一次new時執行一次,之後不在執行。而非靜態程式碼塊每new一次就執行一次。
Java程式碼塊使用
局部程式碼塊
位置:局部位置(方法內部)
作用:限定變數的生命周期,儘早釋放,節約記憶體
調用:調用其所在的方法時執行
public class 局部程式碼塊 { @Test public void test (){ B b = new B(); b.go(); } } class B { B(){} public void go() { //方法中的局部程式碼塊,一般進行一次性地調用,調用完立刻釋放空間,避免在接下來的調用過程中佔用棧空間 //因為棧空間記憶體是有限的,方法調用可能會會生成很多局部變數導致棧記憶體不足。 //使用局部程式碼塊可以避免這樣的情況發生。 { int i = 1; ArrayList<Integer> list = new ArrayList<>(); while (i < 1000) { list.add(i ++); } for (Integer j : list) { System.out.println(j); } System.out.println("gogogo"); } System.out.println("hello"); } }
構造程式碼塊
位置:類成員的位置,就是類中方法之外的位置
作用:把多個構造方法共同的部分提取出來,共用構造程式碼塊
調用:每次調用構造方法時,都會優先於構造方法執行,也就是每次new一個對象時自動調用,對 對象的初始化
class A{ int i = 1; int initValue;//成員變數的初始化交給程式碼塊來完成 { //程式碼塊的作用體現於此:在調用構造方法之前,用某段程式碼對成員變數進行初始化。 //而不是在構造方法調用時再進行。一般用於將構造方法的相同部分提取出來。 // for (int i = 0;i < 100;i ++) { initValue += i; } } { System.out.println(initValue); System.out.println(i);//此時會列印1 int i = 2;//程式碼塊里的變數和成員變數不衝突,但會優先使用程式碼塊的變數 System.out.println(i);//此時列印2 //System.out.println(j);//提示非法向後引用,因為此時j的的初始化還沒開始。 // } { System.out.println("程式碼塊運行"); } int j = 2; { System.out.println(j); System.out.println(i);//程式碼塊中的變數運行後自動釋放,不會影響程式碼塊之外的程式碼 } A(){ System.out.println("構造方法運行"); } } public class 構造程式碼塊 { @Test public void test() { A a = new A(); } }
靜態程式碼塊
位置:類成員位置,用static修飾的程式碼塊 作用:對類進行一些初始化 只載入一次,當new多個對象時,只有第一次會調用靜態程式碼塊,因為,靜態程式碼塊 是屬於類的,所有對象共享一份 調用: new 一個對象時自動調用 public class 靜態程式碼塊 { @Test public void test() { C c1 = new C(); C c2 = new C(); //結果,靜態程式碼塊只會調用一次,類的所有對象共享該程式碼塊 //一般用於類的全局資訊初始化 //靜態程式碼塊調用 //程式碼塊調用 //構造方法調用 //程式碼塊調用 //構造方法調用 } } class C{ C(){ System.out.println("構造方法調用"); } { System.out.println("程式碼塊調用"); } static { System.out.println("靜態程式碼塊調用"); } }
Java程式碼塊、構造方法(包含繼承關係)的執行順序
這是一道常見的面試題,要回答這個問題,先看看這個實例吧。
一共3個類:A、B、C
其中A是B的父類,C無繼承僅作為輸出
A類:
public class A { static { Log.i("HIDETAG", "A靜態程式碼塊"); } private static C c = new C("A靜態成員"); private C c1 = new C("A成員"); { Log.i("HIDETAG", "A程式碼塊"); } static { Log.i("HIDETAG", "A靜態程式碼塊2"); } public A() { Log.i("HIDETAG", "A構造方法"); } }
B類:
public class B extends A { private static C c1 = new C("B靜態成員"); { Log.i("HIDETAG", "B程式碼塊"); } private C c = new C("B成員"); static { Log.i("HIDETAG", "B靜態程式碼塊2"); } static { Log.i("HIDETAG", "B靜態程式碼塊"); } public B() { Log.i("HIDETAG", "B構造方法"); } }
C類:
public class C { public C(String str) { Log.i("HIDETAG", str + "構造方法"); } }
執行語句:new B();
輸出結果如下:
I/HIDETAG: A靜態程式碼塊 I/HIDETAG: A靜態成員構造方法 I/HIDETAG: A靜態程式碼塊2 I/HIDETAG: B靜態成員構造方法 I/HIDETAG: B靜態程式碼塊2 I/HIDETAG: B靜態程式碼塊 I/HIDETAG: A成員構造方法 I/HIDETAG: A程式碼塊 I/HIDETAG: A構造方法 I/HIDETAG: B程式碼塊 I/HIDETAG: B成員構造方法 I/HIDETAG: B構造方法
得出結論:
執行順序依次為: 父類的靜態成員和程式碼塊 子類靜態成員和程式碼塊 父類成員初始化和程式碼快 父類構造方法 子類成員初始化和程式碼塊 子類構造方法
注意:可以發現,同一級別的程式碼塊和成員初始化是按照程式碼順序從上到下依次執行
看完上面這個demo,再來看看下面這道題,看看你搞得定嗎?
看下面一段程式碼,求執行順序:
class A { public A() { System.out.println("1A類的構造方法"); } { System.out.println("2A類的構造快"); } static { System.out.println("3A類的靜態塊"); } } public class B extends A { public B() { System.out.println("4B類的構造方法"); } { System.out.println("5B類的構造快"); } static { System.out.println("6B類的靜態塊"); } public static void main(String[] args) { System.out.println("7"); new B(); new B(); System.out.println("8"); } }
執行順序結果為:367215421548
為什麼呢?
首先我們要知道下面這5點:
每次new都會執行構造方法以及構造塊。
構造塊的內容會在構造方法之前執行。
非主類的靜態塊會在類載入時,構造方法和構造塊之前執行,切只執行一次。
主類(public class)里的靜態塊會先於main執行。
繼承中,子類實例化,會先執行父類的構造方法,產生父類對象,再調用子類構造方法。
所以題目里,由於主類B繼承A,所以會先載入A,所以第一個執行的是第3句。
從第4點我們知道6會在7之前執行,所以前三句是367。
之後實例化了B兩次,每次都會先實例化他的父類A,然後再實例化B,而根據第1、2、5點,知道順序為2154。
最後執行8
所以順序是367215421548
參考文章
https://blog.csdn.net/likunkun__/article/details/83066062
https://www.jianshu.com/p/6877aae403f7
https://www.jianshu.com/p/49e45af288ea
https://blog.csdn.net/du_du1/article/details/91383128
http://c.biancheng.net/view/976.html
https://blog.csdn.net/evilcry2012/article/details/79499786
https://www.jb51.net/article/129990.htm