Java 的package 機制,是每一位學習Java 的人都必須突破的門檻,所以接下來我們將就這個議題做深入討論,底下假設您剛裝好Java 2 SDK 於d:\jdk1.3.0_01 目錄之中,而且沒有修改任何的環境變數(請使用指令:echo%CLASSPATH%,看看CLASSPATH 這個環境變數是否已經設定,如果該環境變數已經被設定了,那麼您的測試結果將會和底下的測試結果有所不同)。接著我們在D 磁碟機根目錄底下新增一個名為my 的空目錄(d:\my),並以這個空目錄作為我們討論的起始點。
討論一
首先,請您將目錄切換到d:\my 底下,請先試著在命令列中輸入java 或 javac您的螢幕上可能會輸出錯誤訊息如下:

這是因為系統不知道到哪裡去找java.exe 或javac.exe 的緣故。所以您可以試著輸入指令:path d:\jdk1.3.0_01\bin然後您再重新輸入java 或 javac就會看到以下畫面:


接著,請您在d:\my 目錄下新增兩個檔案,分別是:
A.java
public class A
{
public static void main(String[] args)
{
B b1=new B() ;
b1.print() ;
}
}
B.java
public class B
{
public void print()
{
System.out.println("package test") ;
}
}
接著請您在命令列輸入:javac A.java如果您的程式輸入無誤,那麼您就會在d:\my 中看到產生了兩個類別檔,分別是A.class 與B.class。
javac.exe 是我們所謂的Java 編譯器,它具有類似make 的能力。舉例來說,我們的A.java 中用到了B 這個類別,所以編譯器會自動幫您編譯B.java。反過來說,如果您輸入的指令是javacB.java,由於B.java 中並沒有用到類別A,所以編譯器並不會自動幫您編譯A.java。編譯成功之後,請輸入指令:
java A
您會看螢幕上成功地輸出
package test
討論二
請先刪除討論一中所產生的類別檔,接著修改您的兩個原始碼,變成如下所示:
A.java
import edu.nctu.* ;
public class A
{
public static void main(String[] args)
{
B b1=new B() ;
b1.print() ;
}
}
B.java
package edu.nctu ;
public class B
{
public void print()
{
System.out.println("package test") ;
}
}
接著請您在命令列輸入:
javac A.java
如果您的程式輸入無誤,那麼您會看到螢幕上出現了許多錯誤訊息,主要的錯誤在於
A.java:1: package edu.nctu does not exist

意思是說找不到edu.nctu 這個package。其他兩個錯誤都是因為第一個錯誤所衍生。可是不對呀,按照Java 的語法,既然B 類別屬於edu.nctu,所以我們在B.java 之中一開始就使用package edu.nctu ;而A 類別用到了B 類別,所以我們也在A.java 開頭加上了import edu.nctu.* ;
所以程式理論上應該不會發生編譯錯誤才是。好的,接下來請在d:\my 目錄下建立一個名為edu 的目錄,在edu 目錄下再建立一個名為nctu 的子目錄,然後將B.java 複製到d:\my\edu\nctu 目錄下,請重新執行javac A.java如果操作無誤,那麼您的螢幕上會因出兩個錯誤訊息:

請再將d:\my 底下的B.java 刪去,重新執行javac A.java 呼~~ 這次終於編譯成功了!!您會在d:\my 目錄下找到A.class,也會在d:\my\edu\nctu 目錄下找到編譯過的B.class。編譯成功之後,請輸入指令:java A您會看到螢幕上輸出package test
嗯,程式順利地執行了。接下來,請將d:\my\edu\nctu 目錄下的B.class 移除,重新執行java A 螢幕上會輸出錯誤訊息:

意思是說java.exe 無法在edu/nctu 這個目錄下找到B.class。完成了這個實驗之後,請將剛剛移除的B.class 還原,以便往後的討論。到此處,我們得到了兩個重要的結論:
結論1
如果您的類別屬於某個package,那麼您就應該將它至於該package 所對應的相對路徑之下。舉例來說,如果您有個類別叫做C,屬於xyz.pqr.abc 套件,那麼您就必須建立一個三層的目錄 xyz\pqr\abr,然後將C.java 或是C.class 放置到這個目錄下,才能讓javac.exe 或是java.exe 順利執行。其實這裡少說了一個重點,就是這個新建的目錄應該從哪裡開始?一定要從d:\my 底下開始建立嗎?請大家將這個問題保留在心裡,我們將在底下的討論之中為大家說分曉。
結論2
當您使用javac.exe 編譯的時候,類別原始碼的位置一定要根據結論一所說來放置,如果該原始碼出現在不該出現的地方(如上述測試中B.java 同時存在d:\my 與d:\my\edu\nctu 之下),除了很容易造成混淆不清,而且有時候抓不出編譯為何發生錯誤,因為javac.exe 輸出的錯誤訊息根本無法改善問題。在我們做進一步測試討論前,請再做一個測試,請將d:\my\edu\nctu 目錄下的B.class 複製到d:\my 目錄中,執行指令:java A您應當還是會看到螢幕上輸出package test但是此時如果您重新使用指令javac A.java重新編譯A 類別,螢幕上會出現
bad class file: .\B.class class file contains wrong class: edu.nctu.B的錯誤訊息。

最後,請您刪掉d:\my 底下剛剛複製過來的B.class,也刪除d:\my\edu\nctu 目錄中的B.java,也就是讓整個系統中只剩下d:\my\edu\nctu 目錄中擁有B.class,然後再使用指令javac A.java您一定會發現,除了可以通過編譯之外,也可以順利地執行。從這麼冗長的測試中,我們又得到了兩個結論:
結論3
編譯時,如果程式裡用到其他的類別,不需要該類別的原始碼也一樣能夠通過編譯。
結論4
當您使用javac.exe 編譯程式卻又沒有該類別的原始碼時,類別檔放置的位置應該根據結論一所說的方式放置。如果類別檔出現在不該出現的地方(如上述測試中B.class 同時存在d:\my 與d:\my\edu\nctu 之下),有很大的可能性會造成莫名其妙的編譯錯誤。雖然上述的測試中,使用java.exe 執行Java 程式時,類別檔亂放不會造成執行錯誤,但是還是建議您儘量不要這樣做,除了沒有意義之外,這種做法像是一顆不定時炸彈,隨時都有可能造成您的困擾。
待续。。。。