本文首先介紹幾個基本的名次,然后介紹了三種代碼塊的特性和使用方法。
在面試大型公司時,如果遇到大型國企或者大的互聯網私企,筆試中經常遇到代碼塊和代碼加載順序的筆試題。這里做一個總結,也方便各位小伙伴飆車不會飄。
-
名詞解釋
代碼塊
由 { } 包起來的代碼,稱為代碼塊
靜態代碼塊
由 static { } 包起來的代碼,稱為靜態代碼塊。
不同類型變量定義示例:
class Demo{ String x;// 非靜態成員變量,又稱為屬性,對該類不同的對象來說,屬性互不相同
static int y = 32;// 類變量,一個類中只有一個該變量,該類不同的對象共享同一個靜態成員變量
public static void main(String[] args){ int z = 0;// 局部變量,只在方法內部可見,在方法結束后由垃圾收集器自動回收
} }
-
局部代碼塊
位置:局部位置(方法內部)。
作用:限定變量的生命周期,盡早釋放,節約內存。
調用:調用其所在的方法時執行。
方法中的局部代碼塊一般進行一次性地調用,調用完立刻釋放空間,避免在接下來的調用過程中占用棧空間。棧空間內存有限,方法調用可能會生成很多局部變量導致棧內存不足,使用局部代碼塊可以避免此缺陷。
public class 局部代碼塊 {
public static void go() {
// 局部代碼塊
{
int age = 30;
System.out.print("go: " + age);
}
}
public static void main(String[] args) {
go();
}
}
-
構造代碼塊
位置:類成員的位置,即類中方法之外的位置。
作用:把多個構造方法共同的部分提取出來,共用構造代碼塊。
調用:每次調用構造方法時,都會優先於構造方法執行,也就是每次new一個對象時自動調用,實現對象初始化。
public class A { int i = 1; int initValue;//成員變量,初始化交給代碼塊來完成
A(){ System.out.println("構造方法在代碼塊執行后運行"); } { System.out.println("代碼塊從上至下依次運行"); //代碼塊的作用體現於此:在調用構造方法之前,用某段代碼對成員變量進行初始化。 //而不是在構造方法調用時再進行。
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的的初始化還沒開始。
} int j = 2; { System.out.println(j); System.out.println(i);//代碼塊中的變量運行后自動釋放,不會影響代碼塊之外的代碼
} } public class 構造代碼塊 { @Test public void test() { A a = new A(); } }
執行結果
代碼塊從上至下依次運行 4950
1
2
2
1 構造方法在代碼塊執行后運行
-
靜態代碼塊
位置:類成員位置。
作用:對類進行一些初始化,只加載一次。當new多個對象時,只有第一次會調用靜態代碼塊,因為靜態代碼塊和類變量一樣,是屬於類的,所有對象共享一份。
調用: new 一個對象時自動調用。
public class 靜態代碼塊 { @Test public void test() { C c1 = new C(); C c2 = new C(); //結果,靜態代碼塊只會調用一次,類的所有對象共享該代碼塊
System.out.println("我是普通方法"); } } class C{ C(){ System.out.println("構造方法調用"); } { System.out.println("代碼塊調用"); } static { System.out.println("靜態代碼塊調用"); } }
調用結果:
靜態代碼塊調用
代碼塊調用
構造方法調用
代碼塊調用
構造方法調用
我是普通方法
執行順序 靜態代碼塊 —–> 構造代碼塊 ——-> 構造方法
-
筆試題
寫出下列程序輸出結果:
public class HelloA { public HelloA(){ System.out.println("HelloA"); } { System.out.println("I'm A class"); } static { System.out.println("static A"); } } public class HelloB extends HelloA { public HelloB(){ System.out.println("HelloB"); } { System.out.println("I'm B class"); } static { System.out.println("static B"); } public static void main(String[] args) { new HelloB(); } }
執行結果:
分析:首先要知道靜態代碼塊是隨着類的加載而加載,而構造代碼塊和構造方法都是隨着對象的創建而加載。
1,在編譯HelloB.java時,由於HelloB 繼承 HelloA,先加載了HelloA類,因此HelloA類的靜態代碼塊首先執行,而后加載HelloB類,HelloB類的靜態代碼塊執行,這沒什么好說的。
2,然后創建HelloB的對象,大家都知道構造代碼塊優先於構造方法執行,這時候問題來了,這時應該先看HelloB類的構造方法,HelloB類里的構造方法里有一句隱式的super()首先被執行,所以找到HelloA類的構造方法,而HelloA類的構造方法中也有一句隱式的super()執行(調用Object類的構造方法),並沒有什么返回結果,接下來才是在執行HelloA類構造方法的方法體前先執行了HelloA類的構造代碼塊(I'm A class),再執行HelloA類構造方法的方法體(也就是Hello A),最后又回到HelloB類的構造方法中,這時HelloB類的super()已經執行完了,在執行HelloB類構造方法的方法體前先執行HelloB類的構造代碼塊(I'm B class),再執行子類構造方法的方法體(HellB)。
無繼承初始化順序:
有繼承初始化順序:
接下來看一道阿里筆試題:
public class B{ public static B t1 = new B(); public static B t2 = new B(); { System.out.println("構造塊"); } static { System.out.println("靜態塊"); } public static void main(String[] args) { B t =new B(); } }
執行結果:
-
總結
Java代碼初始化順序
由 static 關鍵字修飾的,如類變量和靜態代碼塊,將在類創建實例之前被初始化,而且是按順序從上到下依次被執行。(類變量、靜態代碼塊)屬於類本身,不依賴於類的實例。
沒有 static 關鍵字修飾的(如:實例變量(非靜態變量)、非靜態代碼塊)初始化實際上是會被提取到類的構造器中被執行的,但是會比類構造器中的代碼塊優先執行。實例變量、非靜態代碼塊的地位是相等的,它們將按順序被執行。
容易混淆的一個知識點
靜態方法只允許直接訪問靜態成員,而實例方法中可以訪問靜態成員和實例成員,原因是類還沒有實例化,所以實例成員也沒有被創建,靜態方法中因此也不能用this。
-
Reference