Java 的package 機制 王森

来源:互联网 发布:重庆地税网络发票查询 编辑:程序博客网 时间:2024/04/26 12:41

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 程式時,類別檔亂放不會造成執行錯誤,但是還是建議您儘量不要這樣做,除了沒有意義之外,這種做法像是一顆不定時炸彈,隨時都有可能造成您的困擾。


討論三
請先刪除討論二中所產生的所有類別檔,接著請將d:/my 裡的edu 目錄連同子目錄全部移到(不是複製喔!)d:/底下。然後執行javac A.java糟了,螢幕上又出現三個錯誤,意思是說找不到edu.nctu 這個
package。其他兩個錯誤都是因為第一個錯誤所衍生。

接著請執行修改過的指令javac -classpath d:/ A.java就可以編譯成功,您可以在d:/my 目錄下找到A.class,也可以在d:/edu/nctu 目錄下找到B.class。請執行指令java A又出現錯誤訊息了,意思是說java.exe 無法在edu/nctu 這個目錄下找到B.class。

聰明的你一定想到解決方法了,請執行修改過的指令java -classpath d:/ A可式螢幕上仍然出現錯誤訊息,但是這回java.exe 告訴您他找不到A.class,而不是找不到B.class。

好吧,那再將指令改成java -classpath d:/;. A哈哈!這回終於成功地執行了。

到此,我們歸納出一個新的結論,但是在此之前請回過頭看一次結論一再回來看新結論:

結論5
結論一中我們提到,如果您有個類別叫做C,屬於xyz.pqr.abc套件,那麼您就必須建立一個三層的目錄 xyz/pqr/abr,然後將C.java 或是C.class 放置到這個目錄下,才能讓javac.exe 或是java.exe 順利執行。但是這個新建的目錄應該從哪裡開始呢?答案是:“可以建立在任何地方”。但是您必須告訴java.exe 與
javac.exe 到哪裡去找才行, 告訴的方式就是利用它們的-classpath 選項。
在此測試中,我們把edu/nctu 這個目錄移到d:/之下,所以我們必須使用指令:javac -classpath d:/ A.java
與java -classpath d:/;. A告訴java.exe 與javac.exe 說:“如果你要找尋類別檔,請到-classpath 選項後面指定的路徑去找,如果找不到就算了”。不過這裡又衍生出兩個問題,首先第一個問題是,那麼為什麼
之前的指令都不需要額外指定-classpath 選項呢?這是因為當您執行java.exe 和javac.exe 時,其實他已經自動幫您加了參數,所以不管是討論一或討論二裡所使用的指令,其實執行時的樣子如下:
javac -classpath . A.java或java -classpath . A意思就是說,他們都把當時您所在的目錄當作是-classpath 選項的預設參數。那麼您可以發現,如果是javac.exe 執行時,當時我們所在的路徑是d:/my,所以很自然地,他會自動到d:/my 與d:/my/edu/nctu 尋找需要的檔案,java.exe 也是一樣的道理。但是一旦我們搬移了edu/nctu 之後, 這個預設的參數可以讓javac.exe 找到A.java,但是當它要尋找d:/my/edu/nctu 的時候卻找不到,所以自然發生編譯錯誤了。因此我們必須使用在指令中加上-classpath d:/ , 讓javac.exe 到d:/ 下尋找相對路徑d:/edu/nctu。執行java.exe 時之所以也要在指令中加上-classpath  d:/,也是一樣的道理。
如果您仔細比較兩個修改過的指令,您還是會發現些許的差異,於是引發了第二個問題:為什麼javac.exe 用的是-classpathd:/,而java.exe 如果不用-classpath d:/;.而只用-classpath d:/卻無法執行呢?其實這不是什麼大問題,如果做更多的測試,您將可以發現對javac.exe 來說,不管-classpath 選項如何指定,“目前所在的目錄( 即”.” ) ” 一定會被包含在其中, 但是對java.exe 來說就不是如此了,java.exe 會忠實地使用您所指定的路徑,不會擅加路徑到-classpath 選項之中。那麼您一定會問,如果每次編譯會執行都要打那麼長的指令,那豈不是非常麻煩,雖然java.exe 也有提供一個簡化的選項-cp(功能同-classpath),但是還是很麻煩。為了解決這個問題,所以提供了一個環境變數,名為CLASSPATH,有了這個環境變數可以省掉
您不少麻煩。您可以在命令列打上
Set CLASSPATH=d:/;.

然後您就可以像之前一樣使用指令:javac A.java與java A完成編譯和執行的工作。但是請注意,如果您設定了環境變數CLASSPATH,而在使用java.exe 或javac.exe 的時候也使用了-classpath 選項,那麼環境變數將視為無效,而改為以了-classpath選項所指定的路徑為準。

環境變數CLASSPATH 與-classpath 選項分別所指定的路徑並不會有加成效果。

好的,接下來請利用Winzip 之類的壓縮工具將B.java 封裝到edu.zip 之中,並將edu.zip 放到d:/之下。edu.zip 內容如下圖所示:

請執行javac -classpath d:/edu.zip A.java會出現錯誤訊息如下:

前面既然說過編譯或執行都可以不需要原始碼,所以改將B.class封在edu.zip 之中,edu.zip 的內容變成如下所示:

請重新執行javac -classpath d:/edu.zip A.java您會發現可以通過編譯。同時您也可以使用
java -classpath d:/edu.zip;. A一樣可以成功地執行。到這裡我們又歸納出一個新結論:
結論6
ZIP 檔的效力和單純的目錄是一樣的,如果您在-classpath 選項指定了目錄,就是告訴java.exe 和javac.exe 到該目錄尋找類別檔;如果您在-classpath 選項指定了ZIP 檔的檔名,那麼就是請java.exe 和javac.exe 到該壓所檔中尋找類別檔。即使是在壓縮檔中,但是該有的相對路徑還是要有,否則java.exe 和javac.exe 仍然無法找到他們所需要的類別檔。當然,在環境變數CLASSPATH 中也可以指定ZIP 檔作為其內容。為何這此我們要提出ZIP 這種格式的壓縮檔呢?如果您常常使用別人所開發的套件,您一定會發現別人很喜歡使用JAR 檔(.jar)這種檔案格式。其實JAR 檔就是ZIP 檔。為何大家喜歡使用JAR 檔的封裝方式呢?這是因為一般別人所開發的套件總是會附帶許多類別檔和資源檔(例如圖形檔),這麼多檔案全部放在目錄下很容易讓人感到雜亂,如果將這些檔案全部封裝在一個壓縮檔之中,不但可以減少檔案大小,也可以讓您的硬碟看起來更精簡。這也是為何每次我們下載了某人所開發的套件時,如果只有一個JAR 檔,我們就必須在環境變
數CLASSPATH 或-classpath 選項中加上這個JAR 檔的檔名,因為唯有如此,我們才能讓java.exe 和javac.exe 找到我們的程式中所使用到別人套件中類別的類別檔,並成功執行。在此討論中最後要提的是,CLASSPATH 或-classpath 選項中所指定的路徑或JAR 檔(或ZIP 檔)的先後次序也是有影響的。舉例來說, 我們分別把B.java 放在d:/edu/nctu 與d:/my/edu/nctu 目錄下,然後鍵入指令:javac -classpath d:/ A.java  您將發現編譯器編譯的是d:/edu/nctu 目錄下的B.java。我們如果將指令修改為:javac -classpath .;d:/ A.java  您將發現編譯器編譯的是d:/my/edu/nctu 目錄下的B.java。所以在指定環境變數CLASSPATH 或-classpath 選項時,所給予的目錄名稱或JAR 檔名稱之順序一定要謹慎。前面我們曾說過,對javac.exe 來說,不管-classpath 選項如何指定,“目前所在的目錄(即 . )”一定會被包含在其中。從上述測試可以得知,這個預設的路徑一定是附加在我們額外指定的路徑或JAR 檔之後,即:
javac -classpath d:/ A.java等同於javac -classpath d:/;. A.java
為什麼要特別提醒大家這個問題呢?這是因為常常在寫程式的時候不小心在兩個地方都有相同的套件(這也是我本身的切身之痛),一個比較舊,但指定classpath 的時候該路徑較前,導致雖然另外一個路徑裡有新改版的程式,但是執行或編譯時卻總是跑出舊版程式的執行結果,讓人丈二金剛摸不著腦袋。所以最後還是要提醒您特別注意這個問題。



討論四
請刪除前面討論中製作的所有JAVA 檔和類別檔,並重新在d:/my 目錄下產生一個新檔案 A.java,內容如下:
A.java
package edu.nctu.mot ;
public class A
{
public static void main(String[] args)
{
System.out.println("I'm A") ;
}
}
在此我們把類別A 歸屬到edu.nctu.mot 套件之下。
接著請在命令列輸入指令:javac A.java
咦?您會發現竟然可以成功地編譯,這是怎麼一回事呢?其實這裡的A.java 和前面的B.java 是有些許不同的。雖然A.java 屬於edu.nctu.mot 套件,而B.java 屬於edu.nctu 套件。但是之前我們編譯B.java 時候並非直接編譯它,而是透過javac.exe 類似make的機制間接編譯B.java(因為類別A 中用到了類別B)。可是在這裡,我們直接編譯A.java,沒有透過make 機制,所以可以編譯成功(意思就是說前面討論中用到的B.java,即使不放在該放置的目錄中一樣可以成功編譯,只要它自己以及所用到的類別可以在環境變數CLASSPATH 或-classpath 選項指定的位置找到即可)。好,編譯成功了,現在我們試著執行看看,請輸入:java A
相信您的螢幕上一定出現了許多錯誤。

接下來我們應當根據結論一所說,建立一個名為/edu/nctu/mot的目錄,並將A.java 至於其中,並刪除d:/my 裡面的A.java 和A.class。然後輸入指令javac A.java螢幕上出現錯誤訊息

奇怪了,前面不是說javac.exe 會內定從目前所在目錄尋找類別的原始碼呢?這裡怎麼無法運作呢?其實問題和前面一樣,這是因為現在我們直接要求javac.exe 編譯A.java,而非透過make 機制間接編譯,所以我們的指令無法讓javac.exe 清楚地知道我們的意圖,所以我們必須更改指令內容,您會想到的指令可能有以下幾種:

其中,第一種指令是對Java 比較熟悉的人可能會想到的方法,意義我們會在稍後介紹。第二和第三種指令其實意思是一樣的,只是一個用了相對路徑,一個用了絕對路徑。但是,真正能夠成功編譯A.java 的卻只有第四種指令。所以從這裡可以得知,如果您的專案中充滿了許多類別,請儘量利用javac.exe 的make 機制來自動幫您編譯程式碼,否則可是很辛苦的。現在我們試著執行,請輸入:java A  java.exe 說他找不到類別A。

我們必須更改指令內容,您會想到的指令可能有以下幾種:

第二和第三種指令其實意思是一樣的,只是一個用了相對路徑,一個用了絕對路徑,但是,真正能夠執行的卻只有第一種指令。這個指令的意思是告訴java.exe 說:“請你根據環境變數CLASSPATH或-classpath 選項指定的位置執行相對路徑/edu/nctu/mot 之下的A.class”,前面我們提過,如果您沒有指定環境變數CLASSPATH或-classpath 選項,預設會用目前所在路徑,所以第一種指令真正
執行時應該是:java -classpath . edu.nctu.mot.A
因為當時我們所在的路徑為d:/my,所以很自然地java.exe 會尋找d:/my/edu/nctu/mot 目錄中的A.class 來執行。根據結論五所說,如果你將指令改成java -classpath d:/ edu.nctu.mot.A 那麼就無法執行了,除非您在d:/edu/nctu/mot 裡也有A.class。做了那麼多測試, 歸納了這幾點結論, 最後請大家在利用package 機制的時候務必做到下列幾項工作:
1. 將JAVA 檔和類別檔確實安置在其所歸屬之package 所對應的相對路徑下。
2. 不管使用java.exe 或javac.exe,最好明確指定-classpath選項,如果怕麻煩,使用環境變數CLASSPATH 可以省去您輸入額外指令的時間。請注意在他們所指定的路徑或JAR 檔中是否存有package 名稱和類別名稱相同的類別,因為名稱上的衝突很容易引起混淆。只要確實做到上述事項,就可以減少螢幕上大量與實際問題不相干的錯誤訊息,並確保您在Java 程式設計的旅途中順利航行。

原创粉丝点击