From Gossip@caterpillar

Java Gossip: 關於 static 成員

對於每一個基於相同類別所產生的物件而言,其擁有各自的資料成員,然而在某些時候,您會想要這些物件擁有相同的資料成員,其資料是共享的。

舉個例子來說,在Ball類別中,您會使用到圓周率的這個資料,對於任一個球而言,圓周率都是一樣的,您並不需要讓不同的球物件擁有各自的資料成員來記錄圓周率,而這個記錄的值卻是相同,這只會增加記憶體的消耗而已。

您可以將資料成員宣告為"static",被宣告為"static"的資料成員,它是屬於類別所擁有,而不是個別的物件,您可以將"static"視為個別物件所擁有、共享的資料成員。

要宣告static變數,您只要在宣告資料成員時加上"static"關鍵字就可以了,例如:
public class Ball {
    // ....
    public static double PI = 3.14159; // 宣告static資料
 
    public Ball() {
        // ..
    }
 
    public Ball(double radius, String name) {
        //...
    }
 
    public double getVolumn() {
      // ......
    }
}
 
由於static成員屬於類別所擁有,所以在不使用物件名稱的情況下,您也可以使用類別名稱加上 . 運算子來存取static資料成員,例如:
System.out.println("PI = " + Ball.PI);

static變數同樣遵守public、protected與 private的存取限制,所以若您要在類別之外直接存取static變數,必須注意它的權限(例如必須設定為public成員)。

雖然您也可以在宣告物件之後,使用 . 運算子來存取static資料成員,但是這並不被鼓勵,通常建議使用類別名稱加上 . 運算子來存取,一方面也可以避免與非static成員混淆。

與靜態資料成員類似的,您也可以宣告方法成員為static方法,又稱靜態方法,被宣告為靜態的方法通常是為了提供工具,例如在Ball類別上增加一個角度轉徑度的方法toRadius():
public class Ball {
    ...
    public static double toRadius(double angle) {
         return 3.14159 / 180 * angle;
    }
}

與靜態資料成員一樣的,您可以透過類別名稱使用'.'運算子來存取static方法(當然要注意權限設定,例如設定為public),例如:
System.out.println("角度90等於徑度" + Ball.toRadius(90));
 

靜態資料與靜態方法的作用通常是為了提供共享的資料或工具方法,例如將數學常用常數或計算公式,以static宣告並撰寫,之後您可以把這個類別當作工具,透過類別名稱來管理與取用這些靜態資料或方法,例如像J2SE 所提供的Math類別上,就有Math.PI這個靜態常數,以及Math.Exp()Math.Log()Math.Sin()等靜態方法可以直接使用,另外還有像Integer.parseInt()Integer. MAX_VALUE等也都是靜態方法與靜態資料成員的實際例子。

由於static成員是屬於類別而不是物件,所以當您呼叫static方法時,並不會傳入物件的位置參考,所以static方法中不會有 this參考,由於沒有this參考,所以在Java的static方法成員中不允許使用非static成員,因為程式沒有this來參考至物件位址,也 就無法辨別要存取哪一個物件的成員,事實上,如果您在static方法中使用非static資料成員,在編譯時就會出現以下的錯誤訊息:
non-static variable test cannot be referenced from a static context

或者是在static函式中呼叫非static函式,在編譯時就會出現以下的錯誤訊息:
non-static method showMe() cannot be referenced from a static context

Java在使用到類別時才會加以載入程式中,如果您要使用一個static資料或方法,而在載入一個類別時,您希望先進行一些初始化動作,您可以使用static定義一個區塊,並在當中撰寫初始化資源的動作,例如:
public class Ball {
    public static int[] arr = new int[10];
    static {
        // 一些初始化程式碼
    }

    ....
}
 

像上面這樣一個類別,在第一次呼叫而被載入時,static區塊中的程式碼就會被執行,且只會執行一次,要注意的是,static屬性成員必須撰寫在 static區塊之前,而如果在static區塊中發生了例外,則最後會被包裝為 java.lang.ExceptionInInitializerError。