package 與 import
May 18, 2022就如同你會分不同資料夾來放置不同作用的檔案,類別也應該分門別類地放置。
package 套件
舉例來說,一個應用程式中會有多個類別彼此合作,也有可能由多個團隊共同分工,完成應用程式的某些功能塊,再組合在一起,若應用程式是多個團隊共同合作,又不分門別類放置 .class,那麼若 A 部門寫了 Util
類別並編譯為 Util.class,B 部門也寫了 Util
類別並編譯為 Util.class,當他們要將應用程式整合時,就會發生檔案覆蓋的問題,若現在要統一管理原始碼,也許原始碼也會發生彼此覆蓋問題。
你要有個分門別類管理類別的方式,無論是實體檔案上的分類管理,或是類別名稱上的分類管理,有個 package
關鍵字,可以達到這個目的。
請用編輯器開啟〈類別/原始碼路徑〉中 Hello1/src 資料夾的 Console.java,在開頭鍵入 package
該行:
package cc.openhome.util;
public class Console {
public static void writeLine(String text) {
System.out.println(text);
}
}
這表示,Console
類別將放在 cc.openhome.util
套件(package)管理,套件的命名,通常會用組織或單位的網址命名,舉例來說,我的網址是 openhome.cc,套件就會反過來命名為 cc.openhome
,由於組織或單位的網址是獨一無二的,這樣的命名方式,比較不會與其他組織或單位的套件發生同名衝突。
接著編輯 Hello1/src 資料夾中的 Main.java,在開頭鍵入 package
該行文字,這表示 Main
類別將放在 cc.openhome
套件,並且將 Console
改為 cc.openhome.util.Console
:
package cc.openhome;
public class Main {
public static void main(String[] args) {
cc.openhome.util.Console.writeLine("Hello, World");
}
}
當類別原始碼開始使用 package
進行分類時,就會具有四種管理上的意義:
- 原始碼檔案要放置在與
package
定義名稱階層相同的資料夾階層。
目前計劃將所有原始碼檔案放在 src 中管理,由於 Console
類別使用 package
定義在 cc.openhome.util
套件下,Console.java 就必須放在 src 資料夾中的 cc/openhome/util 資料夾,在沒有工具輔助下,必須手動建立出資料夾,Main
類別使用 package
定義在 cc.openhome
套件下,因此 Main.java 必須放在 src 資料夾中的 cc/openhome 資料夾。
這麼做的好處很明顯,日後若不同組織或單位的原始碼要放置在一起管理,就不容易發生原始碼檔案彼此覆蓋的問題。
package
定義名稱與class
定義名稱,會結合而成類別的完全吻合名稱(Fully qualified name)。
由於 Main
類別是位於 cc.openhome
套件分類中,完全吻合名稱是 cc.openhome.Main
,而 Console
類別是位於 cc.openhome.util
分類中,完全吻合名稱為 cc.openhome.util.Console
。
在原始碼中指定使用某個類別時,如果是相同套件中的類別,就只要使用 class
定義的名稱即可,而不同套件的類別,必須使用完全吻合名稱。由於 Main
與 Console
類別是位於不同的套件中,在 Main
類別中要使用 Console
類別,就必須使用 cc.openhome.util.Console
。
這麼做的好處在於,若另一個組織或單位也使用 class
定義了 Console
,但套件定義為 com.abc
,完全吻合名稱會是 com.abc.Consol
e,也就不會與 cc.openhome.util.Console
發生名稱衝突問題。
- 位元碼檔案要放置在與
package
定義名稱階層相同的資料夾階層。
目前計劃將位元碼檔案放在 classes 資料夾中管理,由於 Console
類別使用 package
定義在 cc.openhome.util
套件下,編譯出來的 Console.class 就必須放在 classes 資料夾中的 cc/openhome/util 資料夾,Main
類別使用 package
定義在 cc.openhome
套件下,Main.class 就必須放在 classes 資料夾中的 cc/openhome 資料夾。
編譯時並不用手動建立對應套件階層的資料夾,若使用 -d
指定位元碼的存放位置,就會自動建立出對應套件階層的資料夾,並將編譯出來的位元碼檔案放置至對應的位置。例如:
C:\workspace\Hello1>javac -sourcepath src -cp classes -d classes src/cc/openhome/Main.java
C:\workspace\Hello1>java -cp classes cc.openhome.Main
Hello, World
由於 Main
類別位於 cc.openhome
套件,使用 java
執行程式時,必須指定完全吻合名稱,也就是指定 cc.openhome.Main
這個名稱。
- 要在套件間共用的類別或方法(Method)必須宣告為
public
。
這牽涉到套件間的權限管理,之後才會討論。
使用 import
使用套件管理,解決了實體檔案與撰寫程式時類別名稱衝突的問題,然而,若每次撰寫程式時,都得鍵入完全吻合名稱,也是件麻煩的事,想想看,有些套件定義的名稱冗長時,單是要鍵入完全吻合名稱得花多少時間。
可以用 import
偷懶一下,例如:
package cc.openhome;
import cc.openhome.util.Console;
public class Main {
public static void main(String[] args) {
Console.writeLine("Hello, World");
}
}
編譯與執行時的指令方式,與方才相同。當編譯器剖析 Main.java 看到 import
宣告時,會先記得 import
的名稱,後續剖析程式時,若看到 Console
名稱,原本會不知道 Console
是什麼東西,但編譯器記得 import
告訴過它,如果遇到不認識的名稱,可以比對一下 import
過的名稱,編譯器試著使用 cc.openhome.util.Console
,結果可以在指定的類別路徑中,cc/openhome/util 資料夾下找到 Console.class,於是進行編譯。
import
只是告訴編譯器,遇到不認識的類別名稱,可以嘗試使用 import
過的名稱,import
讓你少打一些字,讓編譯器多為你做一些事。
如果同一套件下會使用到多個類別,你也許會多次使用 import
:
import cc.openhome.Message;
import cc.openhome.User;
import cc.openhome.Address;
你可以更偷懶一些,使用 *
,表示套件中的全部類別:
import cc.openhome.*;
偷懶也是有個限度,如果編譯器無法辨別該使用哪個類別時,就會引發編譯錯誤,遇到這種情況時,就不能偷懶了,要使用哪個類別名稱,就得明確地在程式中使用類別的完全吻合名稱,就像方才一開始必須撰寫 cc.openhome.util.Console.writeLine("Hello, World")
那樣。
標準 API 有許多常用類別,像 System
類別,其實也有使用套件管理,完整名稱是 java.lang.System
,在 java.lang
套件的類別由於很常用,不用撰寫 import
也可以直接使用 class
定義的名稱,這也就是不用如下撰寫程式的原因(寫了也沒關係,只是自找麻煩):
java.lang.System.out.println("Hello!World!");
如果類別位於同一套件,彼此使用並不需要 import
,當編譯器看到一個沒有套件管理的類別名稱,會先在同一套件尋找類別,如果找到就使用,若沒找到,再試著從 import
陳述進行比對。java.lang
可視為預設就有 import
,沒有寫任何 import
陳述時,也會試著比對 java.lang
的組合,看看是否能找到對應類別。