在編程過程中我們可能會遇到如下這種形式的程序:
public class Test {
{
////
}
}
這種形式的程序段我們將其稱之為代碼塊,所謂代碼塊就是用大括號({})將多行代碼封裝在一起,形成一個獨立的數(shù)據(jù)體,用于實現(xiàn)特定的算法。一般來說代碼塊是不能單獨運行的,它必須要有運行主體。在 Java 中代碼塊主要分為四種:
普通代碼塊是我們用得最多的也是最普遍的,它就是在方法名后面用{}括起來的代碼段。普通代碼塊是不能夠單獨存在的,它必須要緊跟在方法名后面。同時也必須要使用方法名調(diào)用它。
public class Test {
public void test(){
System.out.println("普通代碼塊");
}
}
想到靜態(tài)我們就會想到 static,靜態(tài)代碼塊就是用 static 修飾的用{}括起來的代碼段,它的主要目的就是對靜態(tài)屬性進(jìn)行初始化。
public class Test {
static{
System.out.println("靜態(tài)代碼塊");
}
}
使用 synchronized 關(guān)鍵字修飾,并使用“{}”括起來的代碼片段,它表示同一時間只能有一個線程進(jìn)入到該方法塊中,是一種多線程保護(hù)機(jī)制。
在類中直接定義沒有任何修飾符、前綴、后綴的代碼塊即為構(gòu)造代碼塊。我們明白一個類必須至少有一個構(gòu)造函數(shù),構(gòu)造函數(shù)在生成對象時被調(diào)用。構(gòu)造代碼塊和構(gòu)造函數(shù)一樣同樣是在生成一個對象時被調(diào)用,那么構(gòu)造代碼在什么時候被調(diào)用?如何調(diào)用的呢?看如下代碼:
public class Test {
/**
* 構(gòu)造代碼
*/
{
System.out.println("執(zhí)行構(gòu)造代碼塊...");
}
/**
* 無參構(gòu)造函數(shù)
*/
public Test(){
System.out.println("執(zhí)行無參構(gòu)造函數(shù)...");
}
/**
* 有參構(gòu)造函數(shù)
* @param id id
*/
public Test(String id){
System.out.println("執(zhí)行有參構(gòu)造函數(shù)...");
}
}
上面定義了一個非常簡單的類,該類包含無參構(gòu)造函數(shù)、有參構(gòu)造函數(shù)以及構(gòu)造代碼塊,同時在上面也提過代碼塊是沒有獨立運行的能力,他必須要有一個可以承載的載體,那么編譯器會如何來處理構(gòu)造代碼塊呢?編譯器會將代碼塊按照他們的順序(假如有多個代碼塊)插入到所有的構(gòu)造函數(shù)的最前端,這樣就能保證不管調(diào)用哪個構(gòu)造函數(shù)都會執(zhí)行所有的構(gòu)造代碼塊。上面代碼等同于如下形式:
public class Test {
/**
* 無參構(gòu)造函數(shù)
*/
public Test(){
System.out.println("執(zhí)行構(gòu)造代碼塊...");
System.out.println("執(zhí)行無參構(gòu)造函數(shù)...");
}
/**
* 有參構(gòu)造函數(shù)
* @param id id
*/
public Test(String id){
System.out.println("執(zhí)行構(gòu)造代碼塊...");
System.out.println("執(zhí)行有參構(gòu)造函數(shù)...");
}
}
運行結(jié)果
public static void main(String[] args) {
new Test();
System.out.println("----------------");
new Test("1");
}
------------
Output:
執(zhí)行構(gòu)造代碼塊...
執(zhí)行無參構(gòu)造函數(shù)...
----------------
執(zhí)行構(gòu)造代碼塊...
執(zhí)行有參構(gòu)造函數(shù)...
從上面的運行結(jié)果可以看出在 new 一個對象的時候總是先執(zhí)行構(gòu)造代碼,再執(zhí)行構(gòu)造函數(shù),但是有一點需要注意構(gòu)造代碼不是在構(gòu)造函數(shù)之前運行的,它是依托構(gòu)造函數(shù)執(zhí)行的。正是由于構(gòu)造代碼塊有這幾個特性,所以它常用于如下場景:
1、初始化實例變量
如果一個類中存在若干個構(gòu)造函數(shù),這些構(gòu)造函數(shù)都需要對實例變量進(jìn)行初始化,如果我們直接在構(gòu)造函數(shù)中實例化,必定會產(chǎn)生很多重復(fù)代碼,繁瑣和可讀性差。這里我們可以充分利用構(gòu)造代碼塊來實現(xiàn)。這是利用編譯器會將構(gòu)造代碼塊添加到每個構(gòu)造函數(shù)中的特性。
2、初始化實例環(huán)境
一個對象必須在適當(dāng)?shù)膱鼍跋虏拍艽嬖?,如果沒有適當(dāng)?shù)膱鼍?,則就需要在創(chuàng)建對象時創(chuàng)建此場景。我們可以利用構(gòu)造代碼塊來創(chuàng)建此場景,尤其是該場景的創(chuàng)建過程較為復(fù)雜。構(gòu)造代碼會在構(gòu)造函數(shù)之前執(zhí)行。
上面兩個常用場景都充分利用構(gòu)造代碼塊的特性,能夠很好的解決在實例化對象時構(gòu)造函數(shù)比較難解決的問題,利用構(gòu)造代碼不僅可以減少代碼量,同時也是程序的可讀性增強(qiáng)了。特別是當(dāng)一個對象的創(chuàng)建過程比較復(fù)雜,需要實現(xiàn)一些復(fù)雜邏輯,這個時候如果在構(gòu)造函數(shù)中實現(xiàn)邏輯,這是不推薦的,因為我們提倡構(gòu)造函數(shù)要盡可能的簡單易懂,所以我們可以使用構(gòu)造代碼封裝這些邏輯實現(xiàn)部分。
從詞面上我們就可以看出他們的區(qū)別。靜態(tài)代碼塊,靜態(tài),其作用級別為類,構(gòu)造代碼塊、構(gòu)造函數(shù),構(gòu)造,其作用級別為對象。
1、靜態(tài)代碼塊,它是隨著類的加載而被執(zhí)行,只要類被加載了就會執(zhí)行,而且只會加載一次,主要用于給類進(jìn)行初始化。
2、構(gòu)造代碼塊,每創(chuàng)建一個對象時就會執(zhí)行一次,且優(yōu)先于構(gòu)造函數(shù),主要用于初始化不同對象共性的初始化內(nèi)容和初始化實例環(huán)境。
3、構(gòu)造函數(shù),每創(chuàng)建一個對象時就會執(zhí)行一次。同時構(gòu)造函數(shù)是給特定對象進(jìn)行初始化,而構(gòu)造代碼是給所有對象進(jìn)行初始化,作用區(qū)域不同。
通過上面的分析,他們?nèi)叩膱?zhí)行順序應(yīng)該為:靜態(tài)代碼塊 > 構(gòu)造代碼塊 > 構(gòu)造函數(shù)。
public class Test {
/**
* 靜態(tài)代碼塊
*/
static{
System.out.println("執(zhí)行靜態(tài)代碼塊...");
}
/**
* 構(gòu)造代碼塊
*/
{
System.out.println("執(zhí)行構(gòu)造代碼塊...");
}
/**
* 無參構(gòu)造函數(shù)
*/
public Test(){
System.out.println("執(zhí)行無參構(gòu)造函數(shù)...");
}
/**
* 有參構(gòu)造函數(shù)
* @param id
*/
public Test(String id){
System.out.println("執(zhí)行有參構(gòu)造函數(shù)...");
}
public static void main(String[] args) {
System.out.println("----------------------");
new Test();
System.out.println("----------------------");
new Test("1");
}
}
-----------
Output:
執(zhí)行靜態(tài)代碼塊...
----------------------
執(zhí)行構(gòu)造代碼塊...
執(zhí)行無參構(gòu)造函數(shù)...
----------------------
執(zhí)行構(gòu)造代碼塊...
執(zhí)行有參構(gòu)造函數(shù)...