CodeGym /課程 /JAVA 25 SELF /存取修飾子

存取修飾子

JAVA 25 SELF
等級 15 , 課堂 1
開放

1. 存取修飾子的概覽

Java 中,類別、欄位、方法與建構子有四種存取層級:

修飾子 可在哪裡存取?
public
到處皆可:類別內、其他類別、其他套件
protected
在類別內、子類別(繼承者)、同一套件的其他類別
(package-private)
僅限套件內(當未明確指定修飾子時)
private
僅限當前類別內

接下來逐一說明——搭配範例、小幽默與一些意想不到的轉折。

public — 公開存取

public 就像向全世界公告:「大家都可以進來!」若類別、欄位、方法或建構子被宣告為 public,便能從任何其他類別存取,甚至位於不同套件也可以。

範例:

public class Cat {
    public String name;
    public void sayMeow() {
        System.out.println("喵!");
    }
}

這個類別及其欄位/方法可以從任何地方存取。當你希望類別對所有人可用時(例如撰寫函式庫),這會很方便。

不過! 公開欄位並不總是好主意(見上一堂課)。通常只會將需要對外提供的功能性方法設為公開,而欄位幾乎一律保持為 private

private — 僅限類別內部存取

private 就像上了密碼鎖的保險箱:除了該類別本身,沒有人能存取這些成員。就連繼承者(子類別)也看不到私有欄位與方法!

範例:

public class Cat {
    private String secretName;

    public void setSecretName(String name) {
        secretName = name;
    }

    public String getSecretName() {
        return secretName;
    }
}

這裡的 secretName 無法從其他類別直接讀取或修改。只有 Cat 本身(或其方法)能做到。這是封裝的基礎:我們隱藏內部細節,並僅透過方法對外提供存取。

protected — 受保護的存取

protected 好比 VIP 通行證:允許該類別本身、其繼承者(即使在其他套件)以及同一套件中的所有類別存取。

範例:

public class Animal {
    protected int age;

    protected void growOlder() {
        age++;
    }
}

任何繼承自 Animal 的類別都能存取欄位 age 與方法 growOlder()

public class Cat extends Animal {
    public void haveBirthday() {
        growOlder();
        System.out.println("這隻貓已經 " + age + " 歲了!");
    }
}

此外,同一套件中的所有類別也可以存取 protected 成員。

(package-private) — 套件內部存取

若未指定任何存取修飾子,該成員會被視為 package-private(或稱「預設存取」)。就像沒有上鎖的門,但只對自己人開放:只能被同一套件內的類別存取。

範例:

class Dog {
    String name; // package-private
    void bark() { // package-private
        System.out.println("汪!");
    }
}

Dog 類別、它的欄位 name 與方法 bark() 只能在同一套件中使用。嘗試自其他套件存取將導致編譯錯誤。

2. 修飾子在欄位與方法上的使用

為什麼欄位幾乎一律設為 private

類別的欄位代表其內部狀態。若將它們設為公開,任何外部程式碼都能隨時修改。這就像讓陌生小孩把你的家具當樂高玩:某天你醒來,發現冰箱被倒放在浴室裡。

封裝不良的範例:

public class Person {
    public String name;
    public int age;
}

良好封裝的範例:

public class Person {
    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

透過方法(getter 與 setter)可以控制外部類別能如何修改欄位。例如,可以禁止出現負的年齡。

何時將方法設為 publicprotectedpackage-private

  • public — 當方法需要對所有人可用。通常是類別的主要功能。
  • protected — 當方法只需要給繼承者或套件內部使用(例如在子類別可能有用的輔助方法)。
  • package-private — 當方法只需要在套件內部使用、但不該對外公開(例如實作細節)。
  • private — 當方法只在類別內部使用(例如內部邏輯的輔助方法)。

範例:

public class BankAccount {
    private double balance;

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    protected void applyInterest() {
        balance *= 1.05;
    }

    void internalAudit() {
        // package-private: 僅供套件內的類別使用
    }

    private void logAction(String action) {
        // 僅供類別內部使用
    }
}

3. 程式碼範例:含有不同存取層級的類別

讓我們建立一個同時擁有公開、私有、受保護與套件層級成員的類別,並嘗試從其他類別存取它們,看看會發生什麼事。

package zoo;

public class Animal {
    public String publicName = "人人可用";
    protected String protectedName = "僅供子類與同套件";
    String packageName = "僅供套件內";
    private String privateName = "僅供 Animal";

    public void publicMethod() {
        System.out.println("公開方法");
    }

    protected void protectedMethod() {
        System.out.println("受保護的方法");
    }

    void packageMethod() {
        System.out.println("套件層級的方法");
    }

    private void privateMethod() {
        System.out.println("私有方法");
    }
}

現在嘗試在同一套件中的另一個類別存取這些成員:

package zoo;

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        System.out.println(animal.publicName);      // OK
        System.out.println(animal.protectedName);   // OK
        System.out.println(animal.packageName);     // OK
        System.out.println(animal.privateName);  // 錯誤:private
        animal.publicMethod();                      // OK
        animal.protectedMethod();                   // OK
        animal.packageMethod();                     // OK
        animal.privateMethod();                  // 錯誤:private
    }
}

再從另一個套件中嘗試存取這些成員:

package other;

import zoo.Animal;

public class Test {
    public static void main(String[] args) {
        Animal animal = new Animal();
        System.out.println(animal.publicName);      // OK
        System.out.println(animal.protectedName); // 錯誤:protected
        System.out.println(animal.packageName);   // 錯誤:package-private
        System.out.println(animal.privateName);   // 錯誤:private
        animal.publicMethod();                      // OK
        animal.protectedMethod();                // 錯誤:protected
        animal.packageMethod();                  // 錯誤:package-private
        animal.privateMethod();                  // 錯誤:private
    }
}

結論:

  • public — 到處都能存取。
  • protected — 可在同一套件與子類別中存取(即使位於其他套件,只要透過繼承即可)。
  • package-private — 僅限套件內。
  • private — 僅限類別內。

4. 最佳實務:如何選擇存取修飾子

最小化可見範圍

能看到(或碰到)你的欄位或方法的程式碼越少越好。只公開外部程式碼真正需要的部分。這稱為最小權限原則(principle of least privilege)。

  • 欄位幾乎一律應為 private。例外僅限真正的常數(public static final),細節將在後續課程說明。
  • 方法僅在屬於對外 API 時才設為 public
  • 輔助方法(內部邏輯)— private
  • 提供給子類別的方法protected
  • 套件內部的服務性方法package-private

為什麼這很重要?

  • 若實作細節被暴露,任何變動都可能破壞他人的程式碼。
  • 類別會變得更難測試與維護。
  • 偶發的錯誤(例如欄位被錯誤修改)可能導致 bug。

有時新手會想:「何必這麼麻煩,不如全部設成 public!」但等到專案逐漸擴大,你就得重寫半個程式,只因為有人直接修改了類別的欄位。

5. 使用存取修飾子時的常見錯誤

錯誤 № 1: 將欄位維持 public 或預設為 package-private。
若未指定修飾子,欄位或方法將可被同一套件內的所有類別存取。當有人開始直接修改你的欄位時,這可能會導致意料之外的狀況。

錯誤 № 2: 嘗試從其他類別存取 private 成員。
編譯器不會允許——你會得到錯誤。但如果你試圖透過 reflection(反射)繞過——歡迎來到充滿 bug 與意外崩潰的世界。

錯誤 № 3: 過度使用 public。
若凡事都宣告為 public,類別就像一個打開的線路盒——任何人都可能拉錯一條線而把一切搞壞。

錯誤 № 4: 沒有把僅供子類別使用的方法設為 protected。
如果方法只為了在子類別擴充時使用,請把它設為 protected,而不是 public

錯誤 № 5: 隱性的 package-private 可見性。
有時會忘了指定修飾子,結果方法對整個套件都可存取。若你原本以為它是 private,這就會成為一個意外。

1
任務
JAVA 25 SELF, 等級 15, 課堂 1
上鎖
讀取數位名片上的姓名 🃏
讀取數位名片上的姓名 🃏
1
任務
JAVA 25 SELF, 等級 15, 課堂 1
上鎖
具有祕密機制的魔法計算機 ➕
具有祕密機制的魔法計算機 ➕
1
任務
JAVA 25 SELF, 等級 15, 課堂 1
上鎖
開發團隊的內部協助 🤝
開發團隊的內部協助 🤝
1
任務
JAVA 25 SELF, 等級 15, 課堂 1
上鎖
數位圖書館管理:誰可以做什麼 🏛️
數位圖書館管理:誰可以做什麼 🏛️
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION