Java面向對象之初始化塊
在程序設計中,讓數據域正確地執行初始化一直是一個亘古不變的真理。
那么,有哪些手段可以初始化數據域呢:
- 在構造器中設置值。
- 在聲明中賦值。
- 使用初始化塊。
本篇探討關於Java中的初始化塊的注意點:Java中的初始化塊是類中的一種成員,但是既沒有名字,也沒有標識,不能夠被調用,它僅僅只是在創建Java對象時隱式執行初始化。
普通初始化塊
-
普通的初始化塊就是非static修飾的。
-
聲明時以花括號
{}
包起代碼,被包住的就是初始化代碼,整體就是一個初始化塊。 -
可以有很多個初始化塊,按順序先后且全部地執行,所以沒什么必要分開,一起就完事。
-
聲明實例變量時指定默認值和普通初始化塊都被看做是對象的初始化代碼,按先后順序執行。
-
初始化塊總是在構造器之前被調用。
-
如果多個重載的構造器有相同且與傳入形參無關的語句可以一起提入初始化塊。
public class NormalBlock {
int a = 5;
{
// a = 6;
System.out.println("初始化塊之后的a為"+a);
}
// {
// int a = 8;
// System.out.println("初始化塊中重新定義了一個a?"+a);
// }
NormalBlock(){
System.out.println("構造器中賦a的值為"+a);
}
}
class NormalTest{
public static void main(String[] args) {
new NormalBlock();
}
}
- 上面注釋語句時,結果如下:
初始化塊之后的a為5
構造器中賦a的值為5
可以看到,在這個例子中,聲明實例變量指定默認值也被看作初始化代碼,且依次執行,先初始化塊,后構造器。(可以試着調換它們的位置驗證一下哈)
- 上面解除注釋語句之后,我對結果是產生疑惑的:
初始化塊之后的a為6
初始化塊中重新定義了一個a?8
構造器中賦a的值為6
我的疑惑點在於,我一開始以為,我在第二個代碼塊中定義的和之前同名的變量a是同一個(然而並不是)這樣也就算了,初始化代碼之后,執行構造器時,調用了a,那么這時這個a調用的是哪個呢,於是產生疑惑,希望知道的小伙伴可以為我指點迷津。
- 我在測試的時候還遇到了
ilegal forward reference
,即前向引用錯誤,如下圖。
{
age = 50;
if(age>40) System.out.println("Father類的初始化塊且age>40");
System.out.println("Father類的第一個初始化塊");
}
int age =20;
產生原因:是因為在還沒有定義該變量時,就引用了它,所以為了避免這樣的錯誤,盡量將初始化塊放在成員變量聲明之后。
靜態初始化塊
和普通的對應的就是靜態初始化塊啦,也就是用static修飾的,也稱為類初始化塊。根據名稱分析,類初始化塊負責對類進行初始化,而普通初始化塊負責對對象執行初始化。
- 以
static{}
的格式包圍對類變量的初始化。 - 由於靜態初始化塊和類相關,負責對類進行初始化,所以總是比普通初始化塊先執行。
- 通常用於對類變量執行初始化處理,而不能對實例變量進行初始化處理。
- 靜態初始化塊屬於類的靜態成員,遵循靜態成員不能訪問非靜態成員的規則:即不能訪問非靜態成員(實例變量和實例方法)。
- 類似地,靜態初始化塊和聲明靜態成員變量時指定初始值都時該類的初始化代碼。
初始化塊與構造器
關於初始化塊與構造器的先后調用順序,結合代碼來理解一下子。
package com.my.pac17;
/**
* @auther Summerday
*/
public class A {
{
System.out.println("A.instance initializer");
}
static {
System.out.println("A.static initializer");
}
public A() {
System.out.println("A.A");
}
}
class B extends A {
{
System.out.println("B.instance initializer");
}
static {
System.out.println("B.static initializer");
}
public B() {
System.out.println("B.B");
}
public B(String m) {
this();
System.out.println("B.B," + m);
}
}
class C extends B {
{
System.out.println("C.instance initializer");
}
static {
System.out.println("C.static initializer");
}
public C() {
super("ccc");
System.out.println("C.C");
}
}
class BTest {
public static void main(String[] args) {
new C();
System.out.println("*******");
new C();
}
}
/*測試結果*/
A.static initializer
B.static initializer
C.static initializer
/*類初始化階段,限制性最頂層父類的靜態初始化塊
然后依次向下,直到執行當前類的靜態初始化塊*/
A.instance initializer
A.A
B.instance initializer
B.B//調用B的無參構造器
B.B,ccc//調用B的帶參構造器
C.instance initializer//最后執行C
C.C
/*對象初始化階段,先執行最頂層父類的初始化塊,
最頂層父類的構造器,然后依次向下,直到執行當前
類的初始化塊、當前類的構造器*/
*******
//不用執行靜態初始化語句
A.instance initializer
A.A
B.instance initializer
B.B
B.B,ccc
C.instance initializer
C.C
- static修飾的靜態初始化塊,總是先被調用,且在繼承關系中,最早的父類中的靜態初始化塊先執行。
- 可以看到,第二次創建子類對象時,就沒有再執行靜態初始化塊中的初始化,因為三個類已經加載成功。
- 普通初始化塊和構造器的執行順序為,普通初始化塊在構造器之前執行,從最早的父類一直到當前類。