零基礎學Java(12)靜態欄位與靜態方法

2022-07-27 12:02:18

靜態欄位與靜態方法

  之前我們都定義的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來存取這個常數。
  如果省略關鍵字staticPI就變成了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)。

 

工廠方法

  靜態方法還有另外一種常見的用途。類似LocalDateNumberFormat的類使用靜態工廠方法來構造物件。我們之前使用過工廠方法LocalDate.nowLocalDate.ofNumberFormat類如下生成不同風格的格式化物件:

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個類StaticTestEmployee,這兩個類分別有一個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方法