1. 導言
在 Java 中,存取修飾子就像家裡的門鎖系統。它們決定了誰、從哪裡可以「進入」你的房間(或你的類別欄位/方法)。如果所有門都敞開——任何人都可以進來改東改西。如果全部上鎖——沒有人能破壞任何東西,但某個時候你自己也可能被困在裡面。
提醒一下:Java 有四種主要的存取層級:
| 修飾子 | 類別內可見 | 套件內可見 | 子類可見 | 其他套件可見 |
|---|---|---|---|---|
|
✔ | |||
| (package) | ✔ | ✔ | ||
|
✔ | ✔ | ✔ | (透過繼承) |
|
✔ | ✔ | ✔ | ✔ |
(package) — 指未顯式指定修飾子。此類成員僅在同一個套件內可見。
2. 存取修飾子的常見錯誤
錯誤 1:使用預設修飾子的欄位與方法(package-private)
新手最常見的錯誤之一——忘了指定存取修飾子。結果欄位或方法在整個套件中都可被存取,而這可能並非你的本意。這會導致同套件中與你的類別無關的其他類別也能修改你物件的內部狀態。
// 錯誤:欄位 name 沒有受到保護!
class User {
String name; // package-private!
}
因此,同一套件中的任何類別都可以這麼寫:
User user = new User();
user.name = "Vasya"; // 無任何限制!
錯誤 2:破壞封裝——公開欄位(public)
第二常見的錯誤是把類別欄位宣告為 public。在學習或寫小範例時看似方便,但在真實專案中幾乎總是不妥。你會失去對資料如何被修改、由誰修改的掌控。
public class Account {
public double balance; // 危險!
}
現在任何程式碼都可以這麼做:
Account acc = new Account();
acc.balance = -1000000; // 那現在要怪誰呢?
錯誤 3:缺少 getter 與 setter
有時開發者會把欄位設為 private,卻忘了提供操作它們的方法。結果是在合理的情況下也無法取得或修改值。
public class Product {
private String name;
// 既沒有 getName(),也沒有 setName()
}
錯誤 4:嘗試從另一個類別存取 private 成員
如果你把欄位或方法宣告為 private,那麼即使在同一個套件中,其他類別也無法存取它。新手常常會驚訝地問為什麼「看不到」該欄位。
public class User {
private String password;
}
public class UserService {
public void resetPassword(User user) {
// user.password = "123"; // 編譯錯誤!
}
}
錯誤 5:關於 protected 的誤解
很多人以為 protected 代表「凡是有繼承就看得到」。但在 Java 中,套件之外對 protected 成員的存取僅能透過繼承,且只限於子類別。這個細節很容易忽略。
package animals;
public class Animal {
protected void sleep() {}
}
package zoo;
import animals.Animal;
public class Dog extends Animal {
public void test() {
sleep(); // OK — 子類
}
}
public class NotADog {
public void test() {
Animal a = new Animal();
// a.sleep(); // 錯誤:不是子類!
}
}
3. 如何做才正確:最佳實務
規則 1:預設將欄位設為 private
這是封裝的核心原則。欄位應對類別本身以外的所有人隱藏。若需要開放存取——請使用 getter/setter。
public class Book {
private String title;
private int pages;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
規則 2:只公開必要的方法
如果方法需要被外界呼叫——將它設為 public。若只在同一套件內需要——維持 package-private。若僅供子類別使用——使用 protected。
規則 3:將可見範圍最小化
可見範圍越小,越能降低偶發錯誤與「不速之客」。沒有必要就不要把方法與欄位設為 public。
規則 4:使用 getter 與 setter 控制存取
這讓你可以在讀寫欄位時加入額外邏輯,例如驗證。
public class Account {
private double balance;
public void setBalance(double balance) {
if (balance < 0) {
throw new IllegalArgumentException("餘額不能為負數!");
}
this.balance = balance;
}
public double getBalance() {
return balance;
}
}
規則 5:不要曝露內部實作
如果你的欄位是陣列或清單,不要直接透過 getter 傳回它——請傳回副本,或只提供必要的方法。
public class Team {
private List<String> members = new ArrayList<>();
// 正確作法:
public List<String> getMembers() {
return new ArrayList<>(members); // 傳回副本
}
}
4. 實戰範例
假設我們有一個類別 LibraryUser,用來描述圖書館使用者。
錯誤實作範例
public class LibraryUser {
public String name;
public int borrowedBooks;
}
在這種情況下,任何程式碼都可以對物件為所欲為:
LibraryUser user = new LibraryUser();
user.name = null;
user.borrowedBooks = -10; // 邏輯?哪門子的邏輯?
正確實作範例(具封裝)
public class LibraryUser {
private String name;
private int borrowedBooks;
public LibraryUser(String name) {
this.name = name;
this.borrowedBooks = 0;
}
public String getName() {
return name;
}
public int getBorrowedBooks() {
return borrowedBooks;
}
public void borrowBook() {
borrowedBooks++;
}
public void returnBook() {
if (borrowedBooks > 0) {
borrowedBooks--;
}
}
}
現在外部程式碼無法直接修改借書數量或使用者名稱。一切都只能透過類別的方法來控管。
5. 實作特性與細節
有時看起來把欄位設為 public 比寫一堆 getter 與 setter 還簡單。但那是陷阱!公開的欄位就像把家門敞開:是,方便,但不太安全。
另一個細節——並非所有欄位都需要同時有 getter 與 setter。若欄位值在物件建立後就不該改變,只提供 getter,並把欄位設為 final:
public class Passport {
private final String number;
public Passport(String number) {
this.number = number;
}
public String getNumber() {
return number;
}
}
另外請記得:如果類別宣告為 public,檔名必須與類別名稱相同!這不完全是關於存取修飾子的議題,但卻是新手非常常見的錯誤。
6. 使用存取修飾子時的常見錯誤
錯誤 №1:忘了為欄位或方法指定存取修飾子。 結果欄位或方法在整個套件中都可見,儘管你並不想要。請總是明確指定修飾子,即使 IDE 沒有提出警告。
錯誤 №2:所有欄位都宣告為 public。 這會扼殺封裝,使你的程式碼既脆弱又不可預期。為了「簡單」的範例所養成的習慣,不應進入正式的生產程式碼。
錯誤 №3:嘗試從另一個類別存取 private 欄位。 Java 不會允許——編譯器會保護你。但如果你想透過反射來「繞過」這點——請思考為何需要這麼做。
錯誤 №4:以為 protected 成員在所有有繼承的地方都能存取。 事實上,在套件之外只能從子類別中存取,且必須透過 this 或子類別的物件。
錯誤 №5:透過 getter 傳回內部集合。 如果傳回內部陣列或清單的參考,外部程式碼就能修改它,進而破壞類別的不變式。
錯誤 №6:透過 setter 設定值時缺乏驗證。 如果不檢查輸入值,物件可能會進到不正確的狀態(例如負的餘額)。
錯誤 №7:方法的可見範圍過大。 有時會把只在套件或類別內需要的方法設為 public。這會暴露不必要的 API,並讓維護更加困難。
GO TO FULL VERSION