1. 程式人生 > >Java基礎------包

Java基礎------包

進行面向物件的設計時,一項基本的考慮是:如何將發生變化的東西與保持不變的東西分隔開

這一點對設計庫來說是特別重要的。

使用庫的使用者(客戶端程式設計師)必須能依賴自己使用的那一部分,並且當庫進行修改時,自己不需要改寫程式碼。
建立庫的使用者(庫的建立者)必須能自由地進行修改與改進,同時保證使用庫中元件的程式碼不會受到變動的影響。

那麼,對於庫的建立者來說,哪些部分已經受到客戶端程式設計師的訪問,哪些沒有呢?

為了解決這個問題,Java推出了“訪問許可權修飾詞”的概念,這些訪問許可權修飾詞允許庫建立者宣告哪些東西是讓客戶端程式設計師使用的,哪些是不可使用的。

許可權修飾詞控制的級別在“最大訪問”和“最小訪問”的範圍之間,包括:public(友好的),protected(受保護的),private(私有的)。

所以,針對上述的那個缺陷,在我們設計庫的時候,應該將庫裡的所有元件都儘可能保持為“private”(私有的),並且只展示出那些想讓客戶程式設計師使用的方法或屬性。

然而,控制誰能訪問那個庫的元件的概念現在仍不是完整的。仍存在這樣一個問題:如何將元件繫結到單獨一個統一的庫單元裡

這是通過Java的package(打包)關鍵字來實現的,而且訪問指示符要受到類在相同的包還是在不同的包裡的影響。

什麼是元件

元件對外暴露一個或多個介面,供外界呼叫。元件內部由多個類來協同實現指定的功能。對於複雜的元件,會包括很多類,還可能包含配置檔案、介面、依賴的庫檔案等,元件也可以包含或者使用其他的元件,構成更大粒度的元件。

包的作用

我們用import關鍵字匯入一個完整的庫時,就會獲得包(Package)。
例: import java.util.*;
它的作用是匯入完整的實用工具(Utility)庫,該庫是屬於標準Java開發工具包的一部分。

匯入單獨一個類,可在import語句裡指定那個類的名字。
例:import java.util.Vector
此時,我們可以自由地使用Vector。然而java.util中的其他任何類仍是不可使用的。

進行這樣匯入的原因

進行這樣的匯入,是為管理“名稱空間”(Name Space)提供一種機制。因為在Java中,所有類成員的名字相互之間都會隔離。

也就是說,位於A類中的方法f不會於類B內的、擁有相同引數列表的方法f發生衝突。

但是,由於類會在執行一個Java程式的時候自動下載,所以,在一個Java程式中,類名不能重複。

在建立一個Java檔案時,建立的檔案通常叫做“編輯單元”,而每個檔案也就是“編輯單元”都必須以一個.java結尾的名字,而這個就是原始檔。
在這個檔案內部,可以有一個公共(public)類,它必須擁有與檔案相同的名字(包括大小寫,但排除.java副檔名),如果不這樣做,編譯器就會報告出錯。每個檔案內都只能有一個public類(同樣地,否則編譯器會報告出錯)。

那個檔案中除了與檔名字相同的類之外剩下的類(如果有的話)可在那個包外面的世介面前隱藏起來,因為它們並非“公共”的(非public),而且它們由用於主public類的“支撐”類組成

編譯一個.java檔案時,對於.java檔案中的每個類,編譯時都會獲得一個輸出檔案,但是是以.class為副檔名。所以,會導致從少量的.java檔案裡有可能獲得數量眾多的.class檔案。

而這些.class檔案就是在jvm上真正執行的可執行檔案。

一個有效的程式就是一系列.class檔案,它們可以封裝和壓縮到一個JAR檔案裡(使用Java1.1提供的jar工具)。Java直譯器負責對這些檔案的尋找、裝載和解釋。

注意點:Java並沒有強制一定要使用直譯器。一些固有程式碼的Java編譯器可生成單獨的可執行檔案。

一個java檔案的開頭

通常情況下,在一個檔案中通常在最開始使用以下非註釋程式碼:
package mypackage;

而該語句的作用是指出這個java檔案是屬於名為mypackage的包下面,而其他人如果想使用這個類,要麼指出完整的名字,要麼與mypackage聯合使用import關鍵字。

例:1、mypackage.test.方法名 2、import mypackage

根據Java包(封裝)的約定,名字內的所有字母都應小寫

作為一名庫的設計者,一定要記住package和import關鍵字允許我們做的事情就是分割單個全域性名稱空間, 保證我們不會遇到名字的衝突。

建立獨一無二的包名

因為在一個java檔案在經過編譯之後,產生多個.class檔案,所以將某個特定包使用的所有.class檔案都置入單個目錄裡是非常有必要的。

也就是說,要利用作業系統的分級檔案結構避免出現混亂的局面,而此時,必須保證包名的獨一無二。

而需要達到這個目的,需要將.class檔案的位置路徑編碼到package的名字裡。

而java直譯器在尋找class檔案的過程如下:

首先,它找到環境變數CLASSPATH(將Java或者具有Java解釋能力的工具——如瀏覽器——安裝到機器中時,通過作業系統進行設定)。CLASSPATH包含了一個或多個目錄,它們作為一種特殊的“根”使用,從這裡展開對.class檔案的搜尋。從那個根開始,直譯器會尋找包名,並將每個點號(句點)替換成一個斜槓,從而生成從CLASSPATH根開始的一個路徑名(所以package foo.bar.baz會變成foo\bar\baz或者foo/bar/baz;具體是正斜槓還是反斜槓由作業系統決定)。隨後將它們連線到一起,成為CLASSPATH內的各個條目(入口)。

以後搜尋.class檔案時,就可從這些地方開始查詢與準備建立的類名對應的名字。此外,它也會搜尋一些標準目錄——這些目錄與Java直譯器駐留的地方有關。

例子:

com.bruceeckel就為我的類建立了獨一無二的全域性名稱(自Java 1.2以來,整個包名都是小寫的)。決定建立一個名為util的庫,最後得到的包名如下:
package com.bruceeckel.util;

現在,可將這個包名作為下述兩個檔案的“名稱空間”使用:

// 建立自己的包時,要求package語句必須是檔案中的第一個“非註釋”程式碼
package com.bruceeckel.simple;

public class Vector {
  public Vector() {
    System.out.println(
      "com.bruceeckel.util.Vector");
  }
} ///:~
package com.bruceeckel.simple;

public class List {
  public List() {
    System.out.println(
      "com.bruceeckel.util.List");
  }
} ///:~

這兩個檔案都置於我自己系統的一個子目錄中:

C:\DOC\JavaT\com\bruceeckel\util

此時我們會發現路徑的第一部分也就是“C:\DOC\JavaT\”是由CLASSPATH環境變數決定的。

在我本地上:CLASSPATH=.;D:\JAVA\LIB;C:\DOC\JavaT

CLASSPATH裡能包含大量備用的搜尋路徑。然而,使用JAR檔案時要注意一個問題:必須將JAR檔案的名字置於類路徑裡,而不僅僅是它所在的路徑。所以對一個名為grape.jar的JAR檔案來說,我們的類路徑需要包括:
CLASSPATH=.;D:\JAVA\LIB;C:\flavors\grape.jar

編譯器遇到import語句後,它會搜尋由CLASSPATH指定的目錄查詢子目錄com\bruceeckel\util,然後查詢名稱適當的已編譯檔案(對於Vector是Vector.class,對於List則是List.class)。注意Vector和List內無論類還是需要的方法都必須設為public。

自動編譯

為匯入的類首次建立一個物件時(或者訪問一個類的static成員時),編譯器會在適當的目錄裡尋找同名的.class檔案(所以如果建立類X的一個物件,就應該是X.class)。

只發現X.class,它就是必須使用的那一個類。然而,如果它在相同的目錄中還發現了一個X.java,編譯器就會比較兩個檔案的日期標記。如果X.java比X.class新,就會自動編譯X.java生成一個最新的X.class
對於一個特定的類,或在與它同名的.java檔案中沒有找到它,就會對那個類採取上述的處理。

導包衝突

若通過*匯入了兩個庫,而且它們包括相同的名字,這時會出現什麼情況呢?例如,假定一個程式使用了下述匯入語句:
import com.bruceeckel.util.*;
import java.util.*;
由於java.util.*也包含了一個Vector類,所以這會造成潛在的衝突。然而,只要衝突並不真的發生,那麼就不會產生任何問題——這當然是最理想的情況,因為否則的話,就需要進行大量程式設計工作,防範那些可能可能永遠也不會發生的衝突。
如現在試著生成一個Vector,就肯定會發生衝突。如下所示:
Vector v = new Vector();
它引用的到底是哪個Vector類呢?編譯器對這個問題沒有答案,讀者也不可能知道。所以編譯器會報告一個錯誤,強迫我們進行明確的說明。例如,假設我想使用標準的Java Vector,那麼必須象下面這樣程式設計:
java.util.Vector v = new java.util.Vector();
由於它(與CLASSPATH一起)完整指定了那個Vector的位置,所以不再需要import java.util.*語句,除非還想使用來自java.util的其他東西。

利用匯入改變行為

Java已取消的一種特性是C的“條件編譯”,它允許我們改變引數,獲得不同的行為,同時不改變其他任何程式碼。Java之所以拋棄了這一特性,可能是由於該特性經常在C裡用於解決跨平臺問題:程式碼的不同部分根據具體的平臺進行編譯,否則不能在特定的平臺上執行。由於Java的設計思想是成為一種自動跨平臺的語言,所以這種特性是沒有必要的。
然而,條件編譯還有另一些非常有價值的用途。一種很常見的用途就是除錯程式碼。除錯特性可在開發過程中使用,但在發行的產品中卻無此功能。Alen Holub(www.holub.com)提出了利用包(package)來模仿條件編譯的概念。根據這一概念,它建立了C“斷定機制”一個非常有用的Java版本。

之所以叫作“斷定機制”,是由於我們可以說“它應該為真”或者“它應該為假”。如果語句不同意你的斷定,就可以發現相關的情況。這種工具在除錯過程中是特別有用的。

例子:

package com.bruceeckel.tools.debug;

public class Assert {
  private static void perr(String msg) {
    System.err.println(msg);
  }
  public final static void is_true(boolean exp) {
    if(!exp) perr("Assertion failed");
  }
  public final static void is_false(boolean exp){
    if(exp) perr("Assertion failed");
  }
  public final static void 
  is_true(boolean exp, String msg) {
    if(!exp) perr("Assertion failed: " + msg);
  }
  public final static void 
  is_false(boolean exp, String msg) {
    if(exp) perr("Assertion failed: " + msg);
  }
}
package com.bruceeckel.tools;

public class Assert {
  public final static void is_true(boolean exp){}
  public final static void is_false(boolean exp){}
  public final static void 
  is_true(boolean exp, String msg) {}
  public final static void 
  is_false(boolean exp, String msg) {}
}

通過改變匯入的package,我們可將自己的程式碼從除錯版本變成最終的發行版本。這種技術可應用於任何種類的條件程式碼。

包的停用

大家應注意這樣一個問題:每次建立一個包後,都在為包取名時,間接地指定了一個目錄結構。這個包必須存在(駐留)於由它的名字規定的目錄內。而且這個目錄必須能從CLASSPATH開始搜尋並發現。最開始的時候,package關鍵字的運用可能會令人迷惑,因為除非堅持遵守根據目錄路徑指定包名的規則,否則就會在執行期獲得大量莫名其妙的訊息,指出找不到一個特定的類——即使那個類明明就在相同的目錄中。若得到象這樣的一條訊息,請試著將package語句作為註釋標記出去。如果這樣做行得通,就可知道問題到底出在哪兒。