靜態域
如果將域定義為 static,每個類中只有一個這樣的域。而每一個物件對於所有的例項域卻都有自己的一份複製。例如,假定需要給每一個僱員賦予唯一的標識碼。這裡給 Employee 類新增一個例項域 id 和一個靜態域 nextld:
class Employee {
private static int nextId = 1;
private int id;
}
現在,每一個僱員物件都有一個自己的 id 域,但這個類的所有例項將共享一個 nextId 域。換句話說,如果有 1000 個 Employee 類的物件,則有 1000 個例項域 id。但是,只有一個靜態域 nextld。即使沒有一個僱員物件,靜態域 nextld 也存在。靜態域它屬於類,而不屬於任何獨立的物件。
靜態常量
靜態變數使用得比較少,但靜態常量卻使用得比較多。例如,在 Math 類中定義了一個靜態常量:
public class Math {
public static final double PI = 3.14159265358979323846;
}
在程式中,可以採用 Math.PI 的形式獲得這個常量。
如果關鍵字 static 被省略,PI 就變成了 Math 類的一個例項域。需要透過 Math 類的物件訪問 PI,並且每一個 Math 物件都有一份它自己的 PI 複製。
另一個多次使用的靜態常量是 System.out。它在 System 類中宣告:
public class System {
public static final PrintStream out = ...;
}
由於每個類物件都可以對公有域進行修改,所以,最好不要將域設計為 public。然而,公有常量(即 public static final 域)卻沒問題。 因為 out 被宣告為 final,所以不允許再將其他列印流賦值給 out。
靜態方法
靜態方法是一種不能向物件實施操作的方法。例如,Math 類的 pow() 方法就是一個靜態方法。表示式 Math.pow(x, a) 計算冪 xa。在計算時不使用任何 Math 物件。換句話說,沒有隱式的引數。
可以認為靜態方法是沒有 this 引數的方法(在一個非靜態的方法中,this 引數列示這個方法的隱式引數。)
Employee 類的靜態方法不能訪問 id 例項域,因為它不能操作物件。但是,靜態方法可以訪問自身類中的靜態域。下面是使用這種靜態方法的一一個示例:
public static int getNextId() {
return nextId; // returns static field
}
可以透過類名呼叫這個方法:int n = Employee.getNextId();
在下面兩種情況下使用靜態方法:
- 一個方法不需要訪問物件狀態,其所需引數都是透過顯式引數提供(例如:Math.pow())
- 一個方法只需要訪問類的靜態域(例如:Employee.getNextId())
Java 中的靜態域與靜態方法在功能上與 C++ 相同。但是,語法書寫上卻稍有所不同。在 C++ 中,使用 :: 運運算元訪問自身作用域之外的靜態域和靜態方法,如 Math::PI。
工廠方法
靜態方法還有另外一種常見的用途。類似 LocalDate 和 NumberFormat 的類使用靜態工廠方法(factory method)來構造物件。你已經見過工廠方法 LocalDate.now() 和 LocalDate.of()。
NumberFormat 類如下使用工廠方法生成不同風格的格式化物件:
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
NumberFormat percentFormatter = NumberFormat.getPercentlnstance();
double x = 0.1;
System.out.println(currencyFormatter.format(x)); // prints $0.10
System.out.println(percentFomatter.format(x)); // prints 10%
為什麼 NumberFormat 類不利用構造器完成這些操作呢?這主要有兩個原因:
- 無法命名構造器。構造器的名字必須與類名相同。但是,這裡希望將得到的貨幣例項和百分比例項採用不用的名字。
- 當使用構造器時,無法改變所構造的物件型別。而 Factory 方法將返回一個 DecimalFormat 類物件,這是 NumberFormat 的子類。
參考資料
《Java核心技術卷一:基礎知識》(第10版)第 4 章:物件與類 4.4 靜態域與靜態方法