有些人寫了一陣子 Java,可是對於 Java 的 package 跟 import 還是不 太瞭解很多人以?樵?悸 .java 檔案中的 import 會讓編譯器把所 import 的程式通通寫到編譯好的 .class 檔案中,或是認? import 跟 C/C++ 的
include 相似,實際上,這是錯誤的觀念。
讓我們先瞭解一下,Java 的 package 到底有何用處。其實,package 名稱就像是我們的姓,而 class 名稱就像是我們的名字 。package 名稱有很多 . 的,就好像是複姓。比如說 java.lang.String,就 是複姓 java.lang,名字? String 的類別;java.io.InputStream 則是複姓 java.io,名字? InputStream 的類別。
Java 會使用 package 這種機制的原因也非常明顯,就像我們取姓名一樣 ,光是一間學校的同一屆同學中,就有可能會出現不少同名的同學,如果不取 姓的話,那學校在處理學生資料,或是同學彼此之間的稱呼,就會發生很大的
困擾。相同的,全世界的 Java 類別數量,恐怕比台灣人口還多,而且還不斷 的在成長當中,如果類別不使用套件名稱,那在用到相同名稱的不同類別時,
就會?生極大的困擾。幸運的是,Java 的套件名稱我們可以自己取,不像人 的姓沒有太大的選擇 ( 所以有很多同名同姓的 ),如果依照 Sun 的規範來取 套件名稱,那理論上不同人所取的套件名稱不會相同 ( 請參閱 "命名慣例"
的相關文章 ),也就不會發生名稱?突的情況。可是問題來了,因?楹芏嗵准?拿?品淺5某ぃ?諦闖淌絞保?岫啻蠔枚
字,花費不少時間,比如說:
| 代碼如下 |
複製代碼 |
java.io.InputStream is = java.lang.System.in; java.io.InputStreamReader isr= new java.io.InputStreamReader(is); java.io.BufferedReader br = new java.io.BufferedReader(isr);
|
在zz.java中,因為引入了包a中的所有類,所以使用起來就好像是在同一個包中一樣(當然首先要滿足存取權限,這裡假定可以訪問)。
在程式中,可以引入包的所有類或若干類。要引入所有類時,可以使用萬用字元“*”,如:
| 代碼如下 |
複製代碼 |
| import java.lang.*; |
實在是不美觀又麻煩。於是,Sun 想了一個辦法,就是 import。
這個 import 就是在程式一開頭的時候,先說明程式中會用到那些類別的
簡稱,也就是只稱呼名字,不稱呼他的姓。首先,在檔案開頭寫:
| 代碼如下 |
複製代碼 |
import java.lang.System; import java.io.InputStream; import java.io.InputStreamReader; import java.io.BufferedReader; |
這幾行說明了這四個姓名的類別,在程式中只用他的名字來稱呼,所以當程式
中提到 System 就是指 java.lang.System,而 InputStream 就是指
java.io.InputStream,依此類推。於是原來的程式就變成:
| 代碼如下 |
複製代碼 |
InputStream = System.in; InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); |
這樣看起來是不是清爽多了呢?如果這些類別用的次數很多,那就更能體會到
import 的好處了。可是這樣還是不夠,因?槔潦僑說奶煨裕?故腔嵊腥司醯
打太多 import 了也很浪費時間,於是 Sun 又提供了一個方法:
| 代碼如下 |
複製代碼 |
import java.lang.*; import java.io.*; |
意思就是,等一下程式中提到的沒有姓名的類別,不是姓 java.lang,就是姓
java.io,如果這兩個?面有同樣名字的類別,而不幸的你又只用名字稱呼這
個類別,那編譯器仍然會跟你抱怨,因?樗?故遣恢?濫闥檔惱飧隼啾鷸改且
個姓的類別。那可不可以再懶一點呢,唯寫:
?v史告訴我們,人可以懶,但不能太懶,這樣是不行的。因?檳切├啾鶚切
java.io 而不是姓 java。就像姓『諸葛』的人應該不會喜歡你稱他?欏褐睢
先生吧。
?檣觴N我一開始說 import 跟 #include 不同呢?因? import 的功能
到此?櫓梗??幌 #include 一樣,會將檔案內容載入進來。import 只是請
編譯器幫你打字,讓編譯器把沒有姓的類別加上姓,並不會把別的檔案的程
式碼寫進來。如果你想練習打字,可以不要使用 import,只要在用到類別的
時候,用它的全部姓名來稱呼它就行了(就像例子一開始那樣),跟使用
import 完全沒有甚?兩樣。
另外,雖然人不可以太懶,但是 Sun 還是幫我們多偷了一點懶。因?
java.lang 這個套件實在是太常太常太常用到了,幾乎沒有程式不用它的,
所以不管你有沒有寫 import java.lang;,編譯器都會自動幫你補上,也就
是說編譯器只要看到沒有姓的類別,它就會自動去 java.lang ?面找找看,
看這個類別是不是屬於這個套件的。所以我們就不用特別去
import java.lang 了。
執行個體
| 代碼如下 |
複製代碼 |
package xx.bb.aa; |
說明這個.java編譯單元中的所有類都放到xx.bb.aa這個package裡面。而對應的,必須把這個.java檔案放在xx目錄下bb目錄下的aa目錄裡面。如果一個.java檔案沒有任何package語句,那麼這個.java裡面的所有類都在package的"/"下面,也稱之為default package。可以看出你一般從任何java教科書上寫的第一個hello world程式的那個類是在defaultpackage裡面的。有了package語句,情況就複雜一點了。這個編譯單元.java必須放在package名對應的目錄之下。而產生的class檔案也要放在對應的目錄結構之下才能正常運作。
例如:
| 代碼如下 |
複製代碼 |
/* A.java */ package aaa.bbb.ccc; public class A{ B b=new B(); } /* B.java*/ package aaa.bbb.ccc; public class B{} |
編譯時間候怎麼填參數呢?我根據package+檔案名稱的格式來寫,
| 代碼如下 |
複製代碼 |
| javac aaa.bbb.ccc.A.java |
漂亮吧?可惜不工作。非要使用合法的路徑名才行:
| 代碼如下 |
複製代碼 |
| javac aaa/bbb/ccc/A.java |
但是你發現產生的class丟失了目錄結構,直接出現在目前的目錄下……
最好的方式是
| 代碼如下 |
複製代碼 |
| javac -d bin aaa/bbb/ccc/A.java |
這樣就會在目前的目錄的bin目錄下看到完整的目錄結構以及放置妥當的class檔案。
package與classpath不得不說的事
對於java來講,所有需要的程式和資源都要以package的形式來組織和讀取。
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
import匯入聲明可分為兩中:
1>單類型匯入(single-type-import)
例:
| 代碼如下 |
複製代碼 |
| import java.util.ArrayList; |
java.util.ArrayList。
而用了import java.util.ArrayList;的話
以後代碼中寫ArrayList就可以了,省事。import可以使用萬用字元*,*代表某package下所有的class,不包括子目錄。
| 代碼如下 |
複製代碼 |
| import java.awt.* |
不等於
| 代碼如下 |
複製代碼 |
import java.awt.* import java.awt.event.* |
如果你要簡寫java.awt.event下和java.awt下的類,你不能偷懶,兩個都要import。
2>按需類型匯入(type-import-on-demand)
例:
| 代碼如下 |
複製代碼 |
| import java.util.*; |
以這樣兩種方式匯入包中的任何一個public的類和介面(只有public類和介面才能被匯入)
*匯入聲明僅匯入類型而不匯入子包;這就是為什麼稱它們為單類型匯入和按需類型匯入聲明的原因.
*匯入的類或介面的簡名(simple name)具有編譯單元範圍.這表示該類型簡名可以在匯入語句所在的編譯單元的任何地方使用.這並不意味著你可以使用該類型所有成員的簡名,而只能使用類型自身的簡名.
例如: java.lang包中的public類都是自動匯入的,包括Math和System類.但是,你不能使用簡名PI()和gc(),而必須使用Math.PI()和System.gc().你不需要鍵入的是java.lang.Math.PI()和java.lang.System.gc().
程式員有時會匯入當前包或java.lang包,這是不需要的,因為當前包的成員本身就在範圍內,而java.lang包是自動匯入的.java編譯器會忽略這些冗餘匯入聲明(redundant import declarations).即使像這樣
| 代碼如下 |
複製代碼 |
import java.util.ArrayList; import java.util.*;
|
多次匯入,也可編譯通過.編譯器會將冗餘匯入聲明忽略.
使用按需匯入聲明是否會降低Java代碼的執行效率?絕對不會!
Java編譯器產生的類檔案僅包含編譯單元實際使用到的類或介面的符號引用.
這是否意味著你總是可以使用按需匯入聲明?是,也不是!
在類似Demo的非正式開發中使用按需匯入聲明顯得很有用.
然而,有這四個理由讓你可以放棄這種聲明:
1>編譯速度:在一個很大的項目中,它們會極大的影響編譯速度.但在小型項目中使用在編譯時間上可以忽略不計.
2>命名衝突:解決避免命名衝突問題的答案就是使用全名.而按需匯入恰恰就是使用匯入聲明初衷的否定.
3>說明問題:全名的使用是自說性的.畢竟進階語言的代碼是給人看的.
4>無名包問題:如果在編譯單元的頂部沒有包聲明,Java編譯器首選會從無名包中搜尋一個類型,然後才是按需型別宣告.如果有命名衝突就會產生問題.
Sun的工程師一般不使用按需類型匯入聲明.這你可以在他們的代碼中找到:
在java.util.Properties類中的匯入聲明:
| 代碼如下 |
複製代碼 |
import java.io.IOException; import java.io.printStream; import java.io.printWrite; import java.io.InputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.util.Hashtable; |
你可以看到有趣的是,她連java.util.Hashtable也匯入,這可是在當前包中啊!
在Java中,若想利用包的特性,可使用引入(import)語句告訴編譯器要使用的類所在的位置。實際上,包名也是類名的一部分。例如,如果abc.FinanceDept包中含有Employee類,則該類可稱作abc.FinanceDept.Employee。如果使用了import語句,再使用類時,包名可省略,只用Employee來指明該類。
引入語句的格式如下:
| 代碼如下 |
複製代碼 |
import pkgl[.pkg2[.pkg3…]].(類名I g-);
例5-18 假設有一個包a,在a中的一個檔案內定義了兩個類xx和YY,其格式如下: package a; c|ass xx{ } class YY{ } 當在另外一個包b中的檔案ZZ.java中使用a中的類時,語句形式如下: //zz.java package b; import a.*; class zz extends XX { YY Y; } |
在zz.java中,因為引入了包a中的所有類,所以使用起來就好像是在同一個包中一樣(當然首先要滿足存取權限,這裡假定可以訪問)。
在程式中,可以引入包的所有類或若干類。要引入所有類時,可以使用萬用字元“*”,如:
| 代碼如下 |
複製代碼 |
| import java.lang.*; |
引入整個包時,可以方便地訪問包中的每一個類。這樣做,語句寫起來很方便,但會佔用過多的記憶體空間,而且代碼下載的時間將會延長。初學者完全可以引入整個包,但是我們建議在瞭解了包的基本內容後,實際用到哪個類,就引入哪個類,盡量不造成資源的浪費。