1. 程式人生 > >JAVA 中import和 package的用法

JAVA 中import和 package的用法

 Java中的一個包就是一個類庫單元,包內包含有一組類,它們在單一的名稱空間之下被組織在了一起。這個名稱空間就是包名。可以使用import關鍵字來匯入一個包。例如使用import java.util.*就可以匯入名稱空間java.util包裡面的所有類。所謂匯入這個包裡面的所有類,就是在import宣告這個包名以後,在接下來的程式中可以直接使用該包中的類。例如:

  1. import java.util.*  
  2. public class SingleImport  
  3. {  
  4.     public static void main(Strin[] args)  
  5.     {  
  6.         ArrayList list=nwe ArrayList();  
  7.     }  
  8. }  

    這裡ArrayList就是java.util包中的一個類,但是由於對程式使用了import關鍵字載入了java.util包,所以這裡並沒有見到對ArrayList類的定義和宣告,也沒有見到該類前面有什麼限定名,就可以直接使用這個類。

       我們之所以要匯入包名,就是要提供一個管理名稱空間的機制。我們知道,如果有兩個類A類和B類都含有一個具有相同特徵標記(引數列表)的方法f(),即便在同一段程式碼中同時使用這兩個方法f(),也不會發生衝突,原因就在於有兩個不同的類名罩在前面作為限定名,所以兩個方法即便同名也不回發生衝突。但是如果類名稱相互衝突又該怎麼辦呢?假設你編寫了一個Apple類並安裝到了一臺機器上,而該機器上已經有一個其他人編寫的Apple類,我們該如何解決呢?因為你如果想弄清楚一臺機器上到底已經安裝了那些類,並不是一件很容易的事情,所以名字之間總是有存在潛在的衝突的可能。在Java中對名稱空間進行完全控制併為每個類建立唯一的識別符號組合就成為了非常重要的事情。如果你要編寫對於同一臺機器上共存的其他Java程式友好的類庫或程式的話,就需要考慮如何防止類名稱之間的衝突問題。

    當編寫一個Java原始碼檔案時,此檔案通常被稱為編譯單元。每個編譯單元都必須有一個字尾名.java,而在編譯單元內有且僅有一個public類,否則編譯器就不會接受。該public類的名稱必須與檔案的名稱相同(包括大小寫,但不包括字尾名.java)。如果在該編譯單元之中還有額外的類的話,那麼在包之外的世界是無法看見這些類的,因為它們不是public類,而且它們主要用來為主public類提供支援。

    當編譯一個.java檔案(即一個編譯單元)時,在.java檔案中的每個類都會有一個輸出檔案,而該輸出檔案的名稱與.java檔案中每個類的名稱相同,只是多了一個字尾名.class。因此在編譯少量.java檔案之後,會得到大量的.class檔案。每一個.java檔案編譯以後都會有一個public類,以及任意數量的非public類。因此每個.java檔案都是一個構件,如果希望許許多多的這樣的構件從屬於同一個群組,就可以在每一個.java檔案中使用關鍵字package。而這個群組就是一個類庫。

    如果使用package語句,它必須是.java檔案中除註釋以外的第一句程式程式碼。如果在檔案的起始處寫:

    package fruit;

就表示你在宣告該編譯單元是名為fruit的類庫的一部分,或者換句話說,你正在宣告該編譯單元中的public類名稱是位於fruit名稱的保護傘下,由fruit名稱罩著。任何想要使用該public類名稱的人都必須指定全名或者與fruit結合使用關鍵字import。

    例如,假設檔案的名稱是Apple.java,這就意味著在該檔案中有且僅有一個public類,該類的名稱必須是Apple(注意大小寫):

  1. package fruit;  
  2. public class Apple  
  3. {  
  4.     //...  
  5. }  

    上面的程式碼已經將Apple類包含在了fruit包中,現在如果有人想使用Apple或者是fruit中的任何其他public類,就必須使用關鍵字import來使fruit中的名稱可用。

  1. import fruit.*;  
  2. public class ImportApple  
  3. {  
  4.     public static void main(String[] args)  
  5.     {  
  6.         Apple a=new Apple();  
  7.     }  
  8. }  

或者使用完整限定名稱:

  1. public class QualifiedApple  
  2. {  
  3.     public static void main(String[] args)  
  4.     {  
  5.         fruit.Apple a=new fruit.Apple();  
  6.     }  
  7. }  

顯然使用關鍵字import使程式碼更加簡潔。

      作為一名程式設計師,我們應該牢記:package和import關鍵字允許做的是將單一的全域性名稱空間分割成各自獨立封閉的名稱空間,使得無論多少人使用Internet以及Java開始編寫類,都不會出現與我們的類名稱相沖突的問題,因為我們的類是被封閉在我們自己定義的獨立的名稱空間裡面的,而非在公共的全域性名稱空間裡面。

      到這裡也許你會發現,其實所謂關鍵字package打包從未將被打包的東西包裝成一個單一的檔案,並且一個包可以由許多.class檔案構成,這就存在將兩個名稱相同的類打進一個包中的可能。為了避免這種情況的發生,一種合乎邏輯的做法就是將特定的所有.class檔案都置於一個目錄下。也就是說利用作業系統的層次化的檔案結構來解決這一問題。這是Java解決混亂問題的一種方式(這裡暫且先不討論JAR包工具)。

      將所有的檔案收入一個子目錄還可以解決另外兩個問題:一、怎樣建立獨一無二的名稱;二、怎樣查詢有可能隱藏於目錄結構中某處的類。

       這些任務是通過將.class檔案所在的路徑位置編碼稱package的名稱來實現的。

      按照慣例,package名稱的第一部分是類的建立者的反順序的Internet域名。為什麼要用Internet域名呢?因為如果你遵照慣例,Internet域名應該是獨一無二的,因此你的package名稱也將是獨一無二的,也就是前面提到的我們自定義的獨立封閉的名稱空間將是獨一無二的,這樣就不會出現名稱衝突的問題了。當然,如果你沒有自己的域名,你就得構造一組不大可能與他人重複的組合(例如你的姓名),來創立獨一無二的package名稱。如果你打算髮布你的Java程式程式碼,稍微花費些代價去取得一個域名還是很有必要的。

      另外,如果你的Java程式程式碼只是在本地計算機上執行,你還可以把package名稱分解為你機器上的一個目錄。所以當Java程式執行並且需要載入.class檔案的時候,它就可以根據package名稱確定.class檔案在目錄上的所處位置。

      程式在執行的時候具體是如何確定.class檔案位置的呢?

      來看看Java直譯器的執行過程吧:首先,找出環境變數CLASSPATH(可以通過作業系統來設定)。CLASSPATH包含一個或多個目錄,用作查詢.class檔案的根目錄。從根目錄開始,直譯器獲取包名稱並將每個句點替換成反斜槓,以從CLASSPATH根中產生一個路徑(例如,package fruit.Apple就變成為fruit/Apple或fruit/Apple或其他,這將取決於作業系統)。得到的路徑會與CLASSPATH中的各個不同的根目錄路徑相連線以獲得一個完整的目錄路徑,直譯器就在這些目錄中查詢與你所需要的類名稱相同的.class檔案。(此外,直譯器還會去查詢某些涉及Java直譯器所在位置的標準目錄。)

      為了理解這一點,以域名Food.net為例。把它的順序倒過來,並且全部轉換為小寫,net.food就成了我們建立類的一個獨一無二的名稱空間。如果我們決定再建立一個名為fruit的類庫,我們可以將該名稱進一步細分,於是得到一個包名如下:

    package net.food.fruit;

    現在,這個包名稱就可以用作下面Apple這個檔案的名稱空間保護傘了:

  1. package net.food.fruit;  
  2.     public class Apple  
  3.     {  
  4.         public Apple()  
  5.         {  
  6.         System.out.println("net.food.fruit.Apple");  
  7.         }  
  8.     }  

這個檔案可能被置於計算機系統中的如下目錄中:

    C:/DOC/JavaT/net/food/fruit

    之所以要放在這個目錄下面是因為前面提到的,便於系統通過CLASSPATH環境變數來找到這個檔案。沿著此路徑往回看就能看到包名net.food.fruit,但是路徑的前半部分怎麼辦呢?交給環境變數CLASSPATH吧,我們可以在計算機中將環境變數CLASSPATH設定如下:

    CHASSPATH=.;D:/JAVA/LIB;C:/DOC/JavaT

    CLASSPATH可以包含多個可供選擇的查詢路徑。每個路徑都用分號隔開,可以看到,上面這個CLASSPATH環境值的第三個路徑就是我們前面檔案的根目錄。如前所述,Java直譯器將首先找到這個根目錄C:/DOC/JavaT,然後將其與包名net.food.fruit相連線,連線的時候將包名中的句點轉換成斜槓,就得到完整的class檔案路徑C:/DOC/JavaT/net/food/fruit。

    需要補充說明的一點,這裡CLASSPATH環境變數關照的是package中的class檔案,如果關照的是JAR包中的class檔案,則會有一點變化,即,必須在CLASSPATH環境變數路徑中將JAR檔案的實際名稱寫清楚,而不僅僅是指明JAR包所在位置目錄。可以想象,因為JAR包所在目錄位置上可能存在很多別的JAR包,而我們需要使用的那個class檔案只會存在於其中一個JAR包裡面,因此可以這樣理解,這裡JAR包實際上也充當了一級檔案目錄的角色,因此要在CLASSPATH環境變數中寫清楚JAR包檔名。例如如果Apple檔案存在於名為fruit.jar的JAR檔案中,則CLASSPATH應寫作:

    CLASSPATH=.;D:/JAVA/LIB;C:/DOC/JavaT/net/food/fruit.jar

    一旦路徑得以正確建立,下面的檔案就可以放於任何目錄之下:

  1. import net.food.fruit.*;  
  2. public class LibTest  
  3. {  
  4.     public static void main(String[] args)  
  5.     {  
  6.         Apple a=new Apple();  
  7.     }  
  8. }  

    當編譯器碰到fruit庫的import語句時,就開始在CLASSPATH所指定的目錄中查詢,查詢過程中分別將CLASSPATH中設定的各項根目錄與包名轉換來的子目錄net/food/fruit相連線,在連線後的完整目錄中查詢已編譯的檔案(即class檔案)找出名稱相符者(對Apple而言就是Apple.class)。找到了這個檔案即匹配到了Apple類。