1. 介紹
在 Java 中,就像在生活裡,並不是所有東西都應該隨時對所有人開放。想像你的公寓:你不會希望鄰居走進你的臥室,對吧?在程式裡也是如此,有時我們想要「關上門」,讓某些變數或方法無法被外部直接存取。
為此,在 Java 中有存取修飾子——一些特殊關鍵字,用來指定某個變數或方法在哪裡可以被使用:例如 public 或 private。
主要的存取修飾子
| 修飾子 | 可見範圍 |
|---|---|
|
在任何看得到該類別的地方(包含其他封包與其他檔案) |
|
僅在同一個類別內 |
|
僅在同一個封包內(亦即同一資料夾中的類別) |
還有其他修飾子,不過等我們談到繼承時再介紹。
實務範例
public class User
{
public String name; // 對所有人可見
private int age; // 僅在 User 類別內可見
public void sayHello()
{
System.out.println("嗨,我叫 " + name);
}
private void secretMethod()
{
System.out.println("這是祕密方法!");
}
}
說明:
- name — 在能使用 User 類別的任何地方都可見。
- age — 僅在 User 類別內可見。
- sayHello() — 公開方法,可從任何其他類別呼叫。
- secretMethod() — 私有方法,無法從外部呼叫。
在實務中如何運作?
假設我們有個表示銀行帳戶的類別。我們當然不希望任何人都能直接改變餘額!因此,餘額欄位會是 private,並提供專門的方法來操作它。
為什麼不該把一切都設成 public?
你可能會有個誘惑:「那就把所有東西都設成 public,省得麻煩!」但這只會走向混亂。想像任何人都能改你的餘額、使用者名稱,甚至呼叫僅供內部使用的方法。在大型程式中,這會導致錯誤與各種「神祕」的 bug。
Java 開發者的黃金法則:
先將一切設為 private,再只對外開放真正需要的部分。
2. 變數的作用域
作用域是指變數「存在」且可被使用的那段程式區域。一旦超出該區域,變數就「消失」了——彷彿它從未存在。
在 Java 中,依作用域來看有幾種變數:
- 區域變數——宣告在方法或程式區塊 { ... } 之內。
- 方法參數——在方法的圓括號中宣告。
- 類別欄位——宣告在類別內、方法之外。
如果變數宣告在 { 區塊 } 之內,
它只在該 區塊 內可見,
在其外部無法存取。
區域變數
區域變數只存在於它被宣告的那個方法或區塊中。
void printSum(int a, int b)
{
int sum = a + b; // 區域變數
System.out.println(sum);
}
// 這裡 sum 已經不存在了!
嘗試在方法之外存取 sum 會造成編譯錯誤:「找不到變數 sum」。
方法參數
參數也是變數,但它們只在方法內存活。
void greet(String name)
{
System.out.println("嗨," + name);
}
// 這裡 name 已經不存在了!
類別欄位(成員變數)
類別欄位宣告在類別中、方法之外。它們在此類別的所有方法中皆可見。
public class Counter
{
private int count = 0; // 類別欄位
public void increment()
{
count++; // 可以使用欄位
}
public int getCount()
{
return count; // 也可以使用欄位
}
}
3. 變數遮蔽 (Shadowing)
遮蔽(shadowing)是指在某個作用域內宣告了與外層作用域同名的變數(或參數)。在該區塊內,「新的」名稱會遮蔽外層的名稱,因此無法直接存取外層的值。
遮蔽範例:
class ShadowDemo
{
int value = 10; // 類別欄位
void printValue()
{
System.out.println(value); // 10 — 輸出類別欄位
int value = 5; // 區域變數遮蔽了類別欄位
System.out.println(value); // 輸出 5,而不是 10
}
}
在此範例中,當我們寫下 int value = 5; 時,是宣告了一個新的區域變數,它的優先序高於同名的類別欄位。於是在方法 printValue() 內存取 value 時,會使用區域變數而非類別欄位。
如果仍需要存取類別的靜態欄位,請使用類別名稱作為前綴:
class ShadowDemo
{
static int value = 10; // 類別的靜態欄位
void printValue()
{
System.out.println(value); // 10 — 類別欄位
int value = 5;
System.out.println(value); // 5 — 區域變數
System.out.println(ShadowDemo.value); // 10 — 類別的靜態欄位,透過 'ShadowDemo' 存取
}
}
若要存取非靜態的類別欄位,請使用關鍵字 this。它指向目前物件的實例。
class ShadowDemo
{
int value = 10;
void printValue()
{
System.out.println(value); // 10 — 類別欄位
int value = 5;
System.out.println(value); // 5 — 區域變數
System.out.println(this.value); // 10 — 類別欄位,透過 'this' 存取
}
}
4. 實作練習:存取修飾子與作用域
讓我們繼續擴充教學用應用程式:一個簡單的學生管理系統。為欄位與方法設定不同的存取修飾子。
範例:Student 類別
public class Student
{
public String name; // 學生姓名(對所有人可見)
private int age; // 年齡(僅在類別內可見)
public Student(String name, int age)
{
this.name = name;
this.age = age;
}
public void sayHello()
{
System.out.println("嗨,我叫 " + name);
}
private void printSecret()
{
System.out.println("我的年齡: " + age);
}
public void revealSecret()
{
printSecret(); // 可在類別內呼叫私有方法
}
}
Student 類別的使用
public class Main
{
public static void main(String[] args)
{
Student s = new Student("Vasya", 20);
s.sayHello(); // OK:公開方法
s.revealSecret(); // OK:公開方法,它會在類別內呼叫私有方法
s.age = 30; // 錯誤!欄位 age 是私有的
s.printSecret(); // 錯誤!方法 printSecret 是私有的
}
}
5. 使用存取修飾子與作用域時的常見錯誤
錯誤 №1:把所有東西都設為 public —— 混亂就此登場!
如果將所有欄位與方法都設為公開,你將失去對資料如何被修改的控制。請將欄位設為 private,並只對外開放必要的部分。
錯誤 №2:嘗試在變數的作用域之外使用它。
例如,在方法內宣告了變數,卻試圖在方法外存取——一定會得到編譯錯誤。區域變數只在它的 { ... } 區塊中存活。
錯誤 №3:名稱衝突(變數遮蔽)。
如果在方法中宣告了與類別欄位相同名稱的變數,很容易混淆你實際使用的是哪一個。請使用 this 來明確指向類別欄位:this.value。
錯誤 №4:從其他類別存取 private 方法或欄位。
若方法或欄位標記為 private,它們只在同一個類別內可見。從外部存取會導致編譯錯誤。
錯誤 №5:忽略了迴圈或區塊的作用域。
在迴圈或任何 { } 區塊內宣告的變數,只在該區塊內存活。區塊外即不存在。
GO TO FULL VERSION