1. 程式人生 > >Java面向物件之初始化塊

Java面向物件之初始化塊

目錄

  • Java面向物件之初始化塊
    • 普通初始化塊
    • 靜態初始化塊
    • 初始化塊與構造器

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修飾的靜態初始化塊,總是先被呼叫,且在繼承關係中,最早的父類中的靜態初始化塊先執行。
  • 可以看到,第二次建立子類物件時,就沒有再執行靜態初始化塊中的初始化,因為三個類已經載入成功。
  • 普通初始化塊和構造器的執行順序為,普通初始化塊在構造器之前執行,從最早的父類一直到當前類。