零基礎學Java(12)靜態字段與靜態方法
靜態字段與靜態方法
之前我們都定義的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
毫無關係。我們建議使用類名而不是對象來調用靜態方法。
在下面兩種情況下可以使用靜態方法:
- 方法不需要訪問對象狀態,因為它需要的所有參數都通過顯式參數提供(例如:Math.pow)
- 方法只需要訪問類的靜態字段(例如:Employee.getNextId)。
工廠方法
靜態方法還有另外一種常見的用途。類似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
的一個子類。
main方法
需要注意,可以調用靜態方法而不需要任何對象。例如,不需要構造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
方法