你撰寫了幾個 Java 類別,一切運作正常。接著你又多寫了幾個。然後你的專案變成 200 個檔案,你再也找不到任何東西。兩個類別擁有相同的名稱,編譯器開始大喊。這正是套件所解決的問題。
Java 中的 套件(package) 是一個命名空間。它將相關的類別、介面和列舉(enum)分組到類似資料夾的結構中。每個 Java 類別都存在於某個套件之中。即使是你簡單的 HelloWorld 類別也有套件,即使你沒有明確撰寫。它會被放入那個臭名昭著的「預設套件」(default package),這對學校作業來說沒問題,但對任何實際專案來說都很糟糕。
Oracle 官方教學說明:「套件是相關型別的群組,提供存取保護與命名空間管理。」這是教科書式的說法,意思是「套件讓你的程式碼不會互相碰撞」。
為什麼套件存在
三個實務上的理由。
第一,名稱衝突。你撰寫了一個 User 類別。你匯入的函式庫也有一個 User 類別。如果沒有套件,Java 無法分辨它們。有了套件,一個是 com.yourcompany.model.User,另一個是 org.somelibrary.model.User。它們可以和平共存。
第二,存取控制。Java 有四種存取層級:private、package-private(預設)、protected 和 public。package-private 是初學者容易忽略的。它代表「同一個套件中的任何類別都可以存取,但套件外部不行」。這不是 bug,而是一種設計工具。你可以對外界隱藏實作細節,同時讓同套件內的相關類別看得到它們。
第三,可發現性。當所有與計費相關的類別都放在 com.yourcompany.billing 時,新進開發者就知道要去哪裡找。這和在電腦上用資料夾整理檔案的邏輯相同。
命名慣例
Java 套件名稱採用反向網域名稱慣例。如果你擁有 jamilur.dev,你的套件名稱會以 dev.jamilur 開頭。如果你在擁有 example.com 的公司工作,則會以 com.example 開頭。
官方 Java 文件建議:「套件名稱應全小寫,以避免與類別或介面名稱衝突。」
以下是真實專案的範例。
-
java.lang-- 核心 Java 語言類別 -
java.util-- 工具類別,如集合框架 -
org.springframework.boot-- Spring Boot -
com.mysql.cj.jdbc-- MySQL JDBC 驅動程式 -
dev.jamilur.ecommerce.model-- 假設的電子商務模型
每個點代表一個目錄。dev.jamilur.ecommerce.model 在磁碟上對應到 dev/jamilur/ecommerce/model/。
宣告套件
將這行放在 Java 檔案的最上方,任何 import 之前。
package dev.jamilur.bkash.app;
Enter fullscreen mode Exit fullscreen mode
就是這樣。檔案必須放在與套件名稱相符的資料夾結構中:dev/jamilur/bkash/app/。
如果你忘記撰寫套件宣告,Java 會將你的類別放入預設套件。實際應用程式請不要這樣做。預設套件無法被位於具名套件中的類別匯入,這表示你會讓自己在專案的其他部分無法使用自己的程式碼。Baeldung 有很好的說明,解釋為什麼預設套件在正式環境中很危險。
實際範例
讓我們為一個孟加拉電子商務應用程式建立一個小型專案結構。
src/
dev/
jamilur/
ecommerce/
model/
Product.java
Customer.java
Order.java
service/
PaymentService.java
ShippingService.java
controller/
OrderController.java
Enter fullscreen mode Exit fullscreen mode
Product.java 檔案開頭為:
package dev.jamilur.ecommerce.model;
public class Product {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
}
Enter fullscreen mode Exit fullscreen mode
而 PaymentService.java 檔案開頭為:
package dev.jamilur.ecommerce.service;
import dev.jamilur.ecommerce.model.Order;
public class PaymentService {
public boolean processPayment(Order order) {
// payment logic here
return true;
}
}
Enter fullscreen mode Exit fullscreen mode
注意 import 敘述。當你想使用其他套件中的類別時,就要匯入它。Order 類別位於 model 套件,PaymentService 類別位於 service 套件。import 就是連接兩者的橋樑。
匯入與萬用字元
你有三種匯入方式。
明確匯入(大部分時候都應該使用此方式)。
import dev.jamilur.ecommerce.model.Order;
import dev.jamilur.ecommerce.model.Customer;
Enter fullscreen mode Exit fullscreen mode
萬用字元匯入(方便但有爭議)。
import dev.jamilur.ecommerce.model.*;
Enter fullscreen mode Exit fullscreen mode
這會匯入 model 套件中的所有類別。它可以節省打字,但缺點是會失去清晰度。閱讀你檔案的人無法知道你具體使用了哪些類別。包含 Google Java 風格指南在內的許多風格指南都建議避免使用萬用字元匯入。你的 IDE 通常會自動將它們展開。
靜態匯入(較少使用,請謹慎使用)。
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
Enter fullscreen mode Exit fullscreen mode
現在你可以直接寫 PI 而不用寫 Math.PI,寫 sqrt(16) 而不用寫 Math.sqrt(16)。這對常數和工具方法來說很方便,但如果過度使用會讓程式碼較難閱讀。
Package-Private 存取權限
以下是大多數教學都會跳過的存取修飾子。
當你宣告類別、方法或欄位時沒有加上任何存取修飾子(沒有 public、沒有 private、沒有 protected),它就會獲得 package-private 存取權限。同一個套件中的任何東西都可以看到它,套件外部則不行。
package dev.jamilur.ecommerce.service;
class PaymentGatewayHelper {
// no 'public' keyword
// only classes in the same package can use this
String encryptData(String raw) {
return "encrypted_" + raw;
}
}
Enter fullscreen mode Exit fullscreen mode
這對不應該成為公開 API 的一部分的內部輔助類別很有用。Spring 大量使用這種模式。你可以為服務方法加上 @Transactional 註解並保持 package-private,Spring 的代理仍然可以攔截它,因為它位於同一個套件中。但外部程式碼無法直接呼叫它。
套件與模組的比較
Java 9 引入了模組(module),可以視為加強版的套件。一個模組會將多個套件綁在一起,並宣告它會匯出哪些套件,以及需要哪些其他模組。
日常專案不需要使用模組。對大多數應用程式來說,套件就足夠了。模組增加了複雜度,只有在大型系統中才值得。Java Platform Module System (JPMS) 的相關說明可參考 官方 JMod 文件,如果你之後想深入了解。
初學者常見困惑
套件名稱會影響效能嗎?不會。套件純粹是組織用途,沒有任何執行時期成本。
不同套件中的兩個檔案可以有相同的類別名稱嗎?可以。這正是套件存在的主要原因之一。com.example.User 和 com.other.User 是兩個完全不同的類別。
我必須讓套件名稱與資料夾結構相符嗎?是的,這是標準 Java 編譯的要求。編譯器會要求如此。Maven 和 Gradle 等現代建置工具也會強制執行。
我可以之後再更改套件名稱嗎?可以,但這會破壞所有 import 與所有參照。你的 IDE 可以自動重構。但在團隊協作的環境中,更改套件名稱會產生合併衝突與混淆。請盡早決定你的套件結構。
快速摘要
- 套件 是一個命名空間,用來將相關的 Java 型別分組。
- 套件名稱遵循反向網域慣例:
com.example.project.module。 - 套件宣告是 Java 檔案中的第一行。
- 你使用
import關鍵字從其他套件匯入類別。 - 為了清晰度,請優先使用明確匯入而非萬用字元匯入。
- package-private 存取權限(無修飾子)只允許同一個套件內的存取。
- 套件對應到檔案系統上的目錄。
- 模組(Java 9+)是在套件之上額外增加的一層,用於大型系統。
套件並不令人興奮。但它們是專案能否順利擴展,與一堆沒有人能理解的檔案之間的差異。及早建立正確的套件結構,可以避免日後的麻煩。
基於 dev.java/learn:Packages
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.