之前我們都定義的main
方法都被標記了static
修飾符,那到底是什麼意思?下面我們來看看
如果將一個欄位定義為static
,每個類只有一個這樣的欄位。而對於非靜態的範例欄位,每個物件都有自己的一個副本。例如,假設需要給每一個員工賦予唯一的標識碼。這裡給Employee
類新增一個範例欄位id和一個靜態欄位nextId
:
class Employee {
// 定義靜態欄位nextId
private static int nextId = 1;
private int id;
}
現在,每一個Employee
物件都有一個自己的id欄位,但這個類的所有範例將共用一個nextId
欄位。換句話說,如果有1000個Employee
類物件,則有1000個範例欄位id,分別對應每一個物件。但是,只有一個靜態欄位nextId
。即使沒有Employee
物件,靜態欄位nextId
也存在。它屬與類,而不屬於任何單個的物件。
下面實現一個簡單的方法:
public void setId() {
id = nextId;
nextId++;
}
假定為harry設定員工標識碼:
harry.setId();
harry的id欄位被設定為靜態欄位nextId
當前的值,並且靜態欄位nextId
的值加1:
harry.id = Employee.nextId;
Employee.nextId++
靜態變數使用的比較少,但靜態常數卻很常用。例如,在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 = ...;
...
}
前面曾經多次提到過,由於每個類物件都可以修改公共欄位,所以,最好不要有公共欄位。然而,公共常數(即final欄位)卻沒問題。因為out
被宣告為final
,所以,不允許再將它重新賦值為另一個列印流:
System.out = new PrintStream(...); // ERROR -- out is final
靜態方法是不在物件上執行的方法。例如,Math
類的pow
方法就是一個靜態方法。表示式Math.pow(x, a)
會計算冪x的a次方。在完成運算時,它並不使用任何Math
物件。換句話說,它沒有隱式引數。
可以認為靜態方法是沒有this
引數的方法(在一個非靜態的方法中,this
引數指示這個方法的隱式引數)
Employee
類的靜態方法不能存取id範例欄位,因為它不能在物件上執行操作。但是,靜態方法可以存取靜態欄位。下面是這樣一個靜態方法的範例:
public static int getNextId() {
return nextId; // returns static field
}
可以提供類名來呼叫這個方法:
int n = Employee.getNextId();
這個方法可以省略關鍵字static
嗎?答案是肯定的。但是,這樣一來,你就需要通過Employee
類物件的參照來呼叫這個方法。
注意:可以使用物件呼叫靜態方法,這是合法的。例如,如果harry是一個Employee
物件,可以用harry.getNextId()
代替Employee.getNextId()
。不過,這種寫法很容易造成混淆,其原因是getNextId
方法計算的結果與harry
毫無關係。我們建議使用類名而不是物件來呼叫靜態方法。
在下面兩種情況下可以使用靜態方法:
靜態方法還有另外一種常見的用途。類似LocalDate
和NumberFormat
的類使用靜態工廠方法來構造物件。我們之前使用過工廠方法LocalDate.now
和LocalDate.of
。NumberFormat
類如下生成不同風格的格式化物件:
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
NumberFormat percentFormatter = NumberFormat.getPercentInstance();
double x = 0.1;
System.out.println(currencyFormatter.format(x)); // prints $0.10
System.out.printIn(percentFormatter.format(x)); // prints 10%
為什麼NumberFormat
類不利用構造器完成這些操作呢?這主要有兩個原因:
DecimalFormat
類的物件,這是NumberFormat
的一個子類。 需要注意,可以呼叫靜態方法而不需要任何物件。例如,不需要構造Math
類的任何物件就可以呼叫Math.pow
同理,main
方法也是一個靜態方法。
public class Application {
public static void main(String[] args) {
// construct objects here
...
}
}
main
方法不對任何物件進行操作。事實上,在啟動程式時還沒有任何物件。靜態的main
方法將執行並構造程式所需要的物件。
提示:每一個類可以由一個main
方法。這是常用於對類進行單元測試的一個技巧
接下來我們建立Employee
類,其中有一個靜態欄位nextId
和一個靜態方法getNextId
。這裡將三個Employee
物件填入一個陣列,然後列印員工資訊。最後,列印出下一個可用的員工標識碼來展示靜態方法。
// 檔案StaticTest.java
public class StaticTest {
public static void main(String[] args) {
System.out.println("1111");
Employee[] staff = new Employee[3];
staff[0] = new Employee("Tom", 40000);
staff[1] = new Employee("Dick", 60000);
staff[2] = new Employee("Harry", 65000);
for (Employee e: staff) {
e.setId();
System.out.println("name=" + e.getName() + ", id=" + e.getId() + ", salary=" + e.getSalary());
}
int n = Employee.getNextId();
System.out.println("Next available id=" + n);
}
}
class Employee {
// 靜態欄位nextId
private static int nextId = 1;
private String name;
private double salary;
private int id;
// 構造器
public Employee(String n, double s) {
name = n;
salary = s;
id = 0;
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public int getId() {
return id;
}
public void setId() {
id = nextId;
nextId++;
}
// 設定靜態方法,靜態方法中能呼叫靜態欄位
public static int getNextId() {
return nextId;
}
public static void main(String[] args) {
Employee e = new Employee("Harry", 5000);
System.out.println(e.getName() + " " + e.getSalary());
}
}
這裡我們定義了2個類StaticTest
和Employee
,這兩個類分別有一個main
函數
執行命令以下命令
java Employee
結果如下:
Harry 5000.0
當我們執行
java StaticTest
結果如下:
name=Tom, id=1, salary=40000.0
name=Dick, id=2, salary=60000.0
name=Harry, id=3, salary=65000.0
Next available id=4
兩者會分別執行各自的main
方法