Thinking in Java(5-2) 訪問權限修飾詞解析及其應用

Thinking in Java(5-2) 訪問權限修飾詞解析及其應用
Photo by orbtal media / Unsplash

在物件導向程式設計中,封裝(Encapsulation) 是一個關鍵概念。透過封裝,我們可以將資料和方法包裝到類別中,並隱藏其具體實作細節。Java 提供了多種訪問權限修飾詞,讓開發者能夠精確控制類別與成員的可見性,從而提高程式的安全性和可維護性。

訪問權限控制與封裝

訪問權限的控制常被稱為具體實作的隱藏。這種機制不僅可以防止外部直接訪問內部資料,還能避免客戶端程式設計師意外地將內部機制當作可用的介面。

  • 設定權限邊界: 將權限的邊界設定在資料型態的內部,確保只有授權的部分才可被外部訪問。
  • 分離介面與實作: 客戶端只能與公開的介面互動,無法直接操縱內部的實作細節。這樣,即使我們更改了非 public 的部分,也不會影響到客戶端的程式碼。

為了增進程式碼的清晰度,建議按照以下順序定義類別的成員:

public class OrganizedByAccess {
    // Public methods
    public void pub1() { /* ... */ }
    public void pub2() { /* ... */ }
    public void pub3() { /* ... */ }

    // Protected methods
    protected void prot1() { /* ... */ }

    // Package-private methods (no modifier)
    void pack1() { /* ... */ }

    // Private methods
    private void priv1() { /* ... */ }
    private void priv2() { /* ... */ }
    private void priv3() { /* ... */ }

    // Private fields
    private int i;
    // ...
}

這種排列方式的好處是,使用者可以從上到下閱讀,先看到對他們最重要的 public 成員,然後依次深入了解。

類別的訪問權限設定

在 Java 中,訪問權限修飾詞不僅可以應用於類別的成員,也可以用來控制整個類別的可見性。

控制類別的訪問權限

要控制某個類別的訪問權限,修飾詞需放在 class 關鍵字之前。

package access;

public class Widget {}

客戶端可以透過以下方式訪問 Widget 類別:

import access.Widget;
// 或
import access.*;

注意事項:

  1. 每個編譯單元(檔案)只能有一個 public 類別。 如果有多個 public 類別,編譯器會報錯。
  2. public 類別的名稱必須與檔案名稱完全匹配(包含大小寫)。 例如,Widget 類別必須存放在 Widget.java 檔案中。若檔案命名為 WIDGET.javawidget.java,編譯器將無法通過。
  3. 編譯單元可以沒有 public 類別。 在這種情況下,檔案命名不受限制。

類別的訪問修飾詞選擇

對於類別本身,Java 只允許兩種訪問權限:

  • public
  • 預設(package-private,沒有修飾詞)

類別不能被宣告為 privateprotected,除非它是內部類別。

範例:

class Soup1 {
  private Soup1() {}
  // (1) Allow creation via static method:
  public static Soup1 makeSoup() {
    return new Soup1();
  }
}

class Soup2 {
  private Soup2() {}
  // (2) Create a static object and return a reference
  // upon request.(The "Singleton" pattern):
  private static Soup2 ps1 = new Soup2();
  public static Soup2 access() {
    return ps1;
  }
  public void f() {}
}

// Only one public class allowed per file:
public class Lunch {
  void testPrivate() {
    // Can't do this! Private constructor:
    //! Soup1 soup = new Soup1();
  }
  void testStatic() {
    Soup1 soup = Soup1.makeSoup();
  }
  void testSingleton() {
    Soup2.access().f();
  }
}

說明:

  • Soup1 類別: 建構子為 private,無法直接創建物件。透過 public 的靜態方法 makeSoup(),可以創建並取得 Soup1 的實例。
  • Soup2 類別: 採用了單例模式(Singleton Pattern),確保類別只能有一個實例。ps1 是一個 private static 成員,透過 public 方法 access() 取得實例。
  • Lunch 類別: 測試了如何使用上述兩個類別。直接創建 Soup1 會失敗,但可以透過 makeSoup() 方法。Soup2 的實例則透過 access() 方法取得。

預設(package-private)訪問權限

如果類別沒有指定訪問修飾詞,則具有預設的 package-private 訪問權限。

  • 可見性: 只能被同一個 package 中的其他類別訪問。
  • 用途: 這種設定允許在 package 內共享類別,但防止外部直接訪問。

注意:

  • 即使類別的某個 static 成員是 public,外部客戶端仍然無法創建該類別的實例,但可以調用其 public static 成員。

小結

透過正確使用 Java 的訪問權限修飾詞,我們可以精確地控制類別和成員的可見性,從而實現資料的封裝和介面與實作的分離。這不僅提高了程式的安全性,也使得程式碼更易於維護和擴展。