Thinking in Java(5-1) 訪問權限修飾詞與 Package 的使用
在 Java 中,訪問權限修飾詞(access specifiers) 讓程式庫的開發者能夠明確指示哪些類別和成員對外部可見,哪些則不可見。這對於維護程式的封裝性和安全性非常重要。
訪問權限修飾詞的層級
從最高權限到最低權限,Java 提供了以下修飾詞:
public
protected
- 預設(package-private,沒有修飾詞)
private
在理解這些修飾詞的作用之前,我們需要先了解 Java 如何整合大量的程式檔案。
Package 的使用
在 Java 中,package 關鍵字用於組織和控制程式庫內的類別。訪問權限修飾詞的效果會因為類別位於相同或不同的 package 而有所不同。
一個 package 包含一組類別,這些類別在同一個命名空間(namespace)下組織。例如,Java 標準工具程式庫 java.util
包含了許多常用的類別,如 ArrayList
。
使用全名引用類別
您可以使用類別的全名來引用它:
public class FullQualification {
public static void main(String[] args) {
java.util.ArrayList list = new java.util.ArrayList();
}
}
這種方式雖然明確,但程式碼會顯得冗長。
使用 import
關鍵字
為了讓程式碼更簡潔,可以使用 import
關鍵字:
import java.util.ArrayList;
public class SingleImport {
public static void main(String[] args) {
ArrayList list = new ArrayList();
}
}
或者,導入整個 package 下的所有類別:
import java.util.*;
public class ImportAll {
public static void main(String[] args) {
ArrayList list = new ArrayList();
HashMap map = new HashMap();
}
}
在這個管理命名空間的機制下,所有類別成員的名稱都是隔離的,不會發生衝突。
編譯單元與命名規則
每個 Java 原始碼檔案稱為編譯單元(compilation unit),必須以 .java
為副檔名。每個編譯單元只能有一個 public
類別,且檔案名稱必須與該類別名稱相同。其他非 public
的類別僅在同一個 package 內可見,通常用來支持主要的 public
類別。
程式碼組織(Code Organization)
當您編譯一個 .java
檔案時,該檔案中的每個類別都會生成一個相同名稱的 .class
檔案。例如,如果您有一個名為 MyClass.java
的檔案,且其中包含兩個類別 MyClass
和 HelperClass
,那麼編譯後會生成 MyClass.class
和 HelperClass.class
。
一個工作中的 Java 程式是由一組 .class
檔案組成的,這些檔案可以打包並壓縮成一個 Java ARchive(JAR) 檔案。Java 解譯器負責查找、載入和解譯這些檔案。
程式庫(Library) 實際上是一組類別檔案,每個檔案都有一個 public
類別和任意數量的非 public
類別。因此,每個檔案都有一個公開的元件。
如果希望這些元件都屬於同一個群組,可以使用關鍵字 package
。package
必須是檔案中除了註解以外的第一行程式碼。
package com.example.myapp;
public class MyClass {
// 類別內容
}
這表示這個編譯單元屬於 com.example.myapp
這個程式庫。任何想要使用這個程式庫的人都必須使用全名或透過 import
關鍵字。
唯一的 package 名稱
為了避免 package 名稱的衝突,一個常見的做法是將特定 package 的所有 .class
檔案都放在同一個目錄下。透過將 .class
檔案的路徑編碼到 package 名稱中,可以建立唯一的名稱,並查找隱藏於目錄結構中的類別。
慣例上,package 名稱的第一部分是類別建立者的網域名稱反轉。 例如,如果您的網域是 example.com
,那麼您的 package 可以命名為 com.example
。在網域和路徑都唯一的情況下,名稱衝突的問題就不會發生。
當 Java 程式執行並需要載入 .class
檔案時,它會透過以下方式確定檔案的位置:
- 查找
CLASSPATH
環境變數: 這是用來指定查找.class
檔案的根目錄。 - 解析 package 名稱: 從根目錄開始,解譯器將 package 名稱中的每個句點
.
替換為系統相依的目錄分隔符號(在 Windows 系統中為\
,在 UNIX 系統中為/
)。例如,com.example.simple
會被解析為com/example/simple
或com\example\simple
。 - 尋找類別檔案: 生成的路徑會與
CLASSPATH
中的每個路徑結合,解譯器會在這些目錄中尋找與要創建的類別名稱相符的.class
檔案。
範例:
package com.example.simple;
public class Vector {
public Vector() {
System.out.println("com.example.simple.Vector");
}
}
package com.example.simple;
public class List {
public List() {
System.out.println("com.example.simple.List");
}
}
假設這兩個檔案位於目錄 C:\MyProject\com\example\simple
下,並且 CLASSPATH
包含了 C:\MyProject
。
當您在程式中使用這些類別時:
import com.example.simple.*;
public class LibTest {
public static void main(String[] args) {
Vector v = new Vector();
List l = new List();
}
}
解譯器會根據 CLASSPATH
和 package 名稱找到相應的 .class
檔案,然後載入並執行。
使用自訂的工具程式庫
您可以利用上述機制建立自己的工具程式庫,減少重複的程式碼。
建立工具類別:
package com.example.util;
import java.io.*;
public class Print {
// Print with a newline:
public static void print(Object obj) {
System.out.println(obj);
}
// Print a newline by itself:
public static void print() {
System.out.println();
}
// Print with no line break:
public static void printnb(Object obj) {
System.out.print(obj);
}
// The new Java SE5 printf() (from C):
public static PrintStream
printf(String format, Object... args) {
return System.out.printf(format, args);
}
}
使用工具類別:
import static com.example.util.Print.*;
public class PrintTest {
public static void main(String[] args) {
print("Available from now on!");
print(100);
print(100L);
print(3.14159);
}
} /* Output:
Available from now on!
100
100
3.14159
*/
透過靜態 import
,您可以直接使用工具類別的方法,而不需要指定類別名稱。
訪問權限修飾詞的詳細解說
預設(package-private)
沒有任何訪問修飾詞的成員僅對同一個 package 中的類別可見,對其他 package 則不可見。
範例:
//: access/dessert/Cookie.java
package access.dessert;
public class Cookie {
public Cookie() {
System.out.println("Cookie constructor");
}
void bite() { System.out.println("bite"); }
}
在其他 package 中:
//: access/Dinner.java
import access.dessert.*;
public class Dinner {
public static void main(String[] args) {
Cookie x = new Cookie();
//! x.bite(); // Can't access
}
} /* Output:
Cookie constructor
*/
public
public
修飾的類別和成員對所有其他類別都可見。
private
private
修飾的成員僅對自身類別可見,對同一個 package 的其他類別也不可見。
範例:
class Sundae {
private Sundae() {}
static Sundae makeASundae() {
return new Sundae();
}
}
public class IceCream {
public static void main(String[] args) {
//! Sundae x = new Sundae();
Sundae x = Sundae.makeASundae();
}
}
protected
protected
修飾的成員對同一個 package 的類別和所有子類別可見,即使子類別在不同的 package 中。
範例:
在 package com.example.cookie2
中:
//: access/cookie2/Cookie.java
package access.cookie2;
public class Cookie {
public Cookie() {
System.out.println("Cookie constructor");
}
protected void bite() {
System.out.println("bite");
}
}
在其他 package 中繼承並使用:
//: access/ChocolateChip2.java
import access.cookie2.*;
public class ChocolateChip2 extends Cookie {
public ChocolateChip2() {
System.out.println("ChocolateChip2 constructor");
}
public void chomp() { bite(); } // Protected method
public static void main(String[] args) {
ChocolateChip2 x = new ChocolateChip2();
x.chomp();
}
} /* Output:
Cookie constructor
ChocolateChip2 constructor
bite
*/
小結
理解 Java 中的訪問權限修飾詞和正確使用 package 對於編寫安全、模組化的程式碼至關重要。利用 public
、protected
、預設(package-private)和 private
,您可以精確控制類別和成員的可見性,從而提高程式的封裝性和可維護性。
Comments ()