<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                # 5.1 包:庫單元 我們用`import`關鍵字導入一個完整的庫時,就會獲得“包”(Package)。例如: ``` import java.util.*; ``` 它的作用是導入完整的實用工具(Utility)庫,該庫屬于標準Java開發工具包的一部分。由于`Vector`位于`java.util`里,所以現在要么指定完整名稱`java.util.Vector`(可省略`import`語句),要么簡單地指定一個`Vector`(因為`import`是默認的)。 若想導入單獨一個類,可在`import`語句里指定那個類的名字: ``` import java.util.Vector; ``` 現在,我們可以自由地使用`Vector`。然而,`java.util`中的其他任何類仍是不可使用的。 之所以要進行這樣的導入,是為了提供一種特殊的機制,以便管理“命名空間”(Name Space)。我們所有類成員的名字相互間都會隔離起來。位于類`A`內的一個方法`f()`不會與位于類`B`內的、擁有相同“簽名”(參數列表)的`f()`發生沖突。但類名會不會沖突呢?假設創建一個`stack`類,將它安裝到已有一個`stack`類(由其他人編寫)的機器上,這時會出現什么情況呢?對于因特網中的Java應用,這種情況會在用戶毫不知曉的時候發生,因為類會在運行一個Java程序的時候自動下載。 正是由于存在名字潛在的沖突,所以特別有必要對Java中的命名空間進行完整的控制,而且需要創建一個完全獨一無二的名字,無論因特網存在什么樣的限制。 迄今為止,本書的大多數例子都僅存在于單個文件中,而且設計成局部(本地)使用,沒有同包名發生沖突(在這種情況下,類名置于“默認包”內)。這是一種有效的做法,而且考慮到問題的簡化,本書剩下的部分也將盡可能地采用它。然而,若計劃創建一個“對因特網友好”或者說“適合在因特網使用”的程序,必須考慮如何防止類名的重復。 為Java創建一個源碼文件的時候,它通常叫作一個“編輯單元”(有時也叫作“翻譯單元”)。每個編譯單元都必須有一個以`.java`結尾的名字。而且在編譯單元的內部,可以有一個公共(`public`)類,它必須擁有與文件相同的名字(包括大小寫形式,但排除`.java`文件擴展名)。如果不這樣做,編譯器就會報告出錯。每個編譯單元內都只能有一個`public`類(同樣地,否則編譯器會報告出錯)。那個編譯單元剩下的類(如果有的話)可在那個包外面的世界面前隱藏起來,因為它們并非“公共”的(非`public`),而且它們由用于主`public`類的“支撐”類組成。 編譯一個`.java`文件時,我們會獲得一個名字完全相同的輸出文件;但對于`.java`文件中的每個類,它們都有一個`.class`擴展名。因此,我們最終從少量的`.java`文件里有可能獲得數量眾多的`.class`文件。如以前用一種匯編語言寫過程序,那么可能已習慣編譯器先分割出一種過渡形式(通常是一個`.obj`文件),再用一個鏈接器將其與其他東西封裝到一起(生成一個可執行文件),或者與一個庫封裝到一起(生成一個庫)。但那并不是Java的工作方式。一個有效的程序就是一系列`.class`文件,它們可以封裝和壓縮到一個JAR文件里(使用Java 1.1提供的`jar`工具)。Java解釋器負責對這些文件的尋找、裝載和解釋(注釋①)。 ①:Java并沒有強制一定要使用解釋器。一些固有代碼的Java編譯器可生成單獨的可執行文件。 “庫”也由一系列類文件構成。每個文件都有一個`public`類(并沒強迫使用一個`public`類,但這種情況最很典型的),所以每個文件都有一個組件。如果想將所有這些組件(它們在各自獨立的`.java`和`.class`文件里)都歸納到一起,那么`package`關鍵字就可以發揮作用)。 若在一個文件的開頭使用下述代碼: ``` package mypackage; ``` 那么`package`語句必須作為文件的第一個非注釋語句出現。該語句的作用是指出這個編譯單元屬于名為`mypackage`的一個庫的一部分。或者換句話說,它表明這個編譯單元內的`public`類名位于`mypackage`這個名字的下面。如果其他人想使用這個名字,要么指出完整的名字,要么與`mypackage`聯合使用`import`關鍵字(使用前面給出的選項)。注意根據Java包(封裝)的約定,名字內的所有字母都應小寫,甚至那些中間單詞亦要如此。 例如,假定文件名是`MyClass.java`。它意味著在那個文件有一個、而且只能有一個`public`類。而且那個類的名字必須是`MyClass`(包括大小寫形式): ``` package mypackage; public class MyClass { // . . . ``` 現在,如果有人想使用`MyClass`,或者想使用`mypackage`內的其他任何`public`類,他們必須用`import`關鍵字激活`mypackage`內的名字,使它們能夠使用。另一個辦法則是指定完整的名稱: ``` mypackage.MyClass m = new mypackage.MyClass(); ``` `import`關鍵字則可將其變得簡潔得多: ``` import mypackage.*; // . . . MyClass m = new MyClass(); ``` 作為一名庫設計者,一定要記住`package`和`import`關鍵字允許我們做的事情就是分割單個全局命名空間,保證我們不會遇到名字的沖突——無論有多少人使用因特網,也無論多少人用Java編寫自己的類。 ## 5.1.1 創建獨一無二的包名 大家或許已注意到這樣一個事實:由于一個包永遠不會真的“封裝”到單獨一個文件里面,它可由多個`.class`文件構成,所以局面可能稍微有些混亂。為避免這個問題,最合理的一種做法就是將某個特定包使用的所有`.class`文件都置入單個目錄里。也就是說,我們要利用操作系統的分級文件結構避免出現混亂局面。這正是Java所采取的方法。 它同時也解決了另兩個問題:創建獨一無二的包名以及找出那些可能深藏于目錄結構某處的類。正如我們在第2章講述的那樣,為達到這個目的,需要將`.class`文件的位置路徑編碼到`package`的名字里。但根據約定,編譯器強迫`package`名的第一部分是類創建者的因特網域名。由于因特網域名肯定是獨一無二的(由InterNIC保證——注釋②,它控制著域名的分配),所以假如按這一約定行事,`package`的名稱就肯定不會重復,所以永遠不會遇到名稱沖突的問題。換句話說,除非將自己的域名轉讓給其他人,而且對方也按照相同的路徑名編寫Java代碼,否則名字的沖突是永遠不會出現的。當然,如果你沒有自己的域名,那么必須創造一個非常生僻的包名(例如自己的英文姓名),以便盡最大可能創建一個獨一無二的包名。如決定發行自己的Java代碼,那么強烈推薦去申請自己的域名,它所需的費用是非常低廉的。 ②:ftp://ftp.internic.net 這個技巧的另一部分是將`package`名解析成自己機器上的一個目錄。這樣一來,Java程序運行并需要裝載`.class`文件的時候(這是動態進行的,在程序需要創建屬于那個類的一個對象,或者首次訪問那個類的一個`static`成員時),它就可以找到`.class`文件駐留的那個目錄。 Java解釋器的工作程序如下:首先,它找到環境變量`CLASSPATH`(將Java或者具有Java解釋能力的工具——如瀏覽器——安裝到機器中時,通過操作系統進行設定)。`CLASSPATH`包含了一個或多個目錄,它們作為一種特殊的“根”使用,從這里展開對`.class`文件的搜索。從那個根開始,解釋器會尋找包名,并將每個點號(句點)替換成一個斜杠,從而生成從`CLASSPATH`根開始的一個路徑名(所以`package foo.bar.baz`會變成`foo\bar\baz`或者`foo/bar/baz`;具體是正斜杠還是反斜杠由操作系統決定)。隨后將它們連接到一起,成為`CLASSPATH`內的各個條目(入口)。以后搜索`.class`文件時,就可從這些地方開始查找與準備創建的類名對應的名字。此外,它也會搜索一些標準目錄——這些目錄與Java解釋器駐留的地方有關。 為進一步理解這個問題,下面以我自己的域名為例,它是`bruceeckel.com`。將其反轉過來后,`com.bruceeckel`就為我的類創建了獨一無二的全局名稱(`com`,`edu`,`org`,`net`等擴展名以前在Java包中都是大寫的,但自Java 1.2以來,這種情況已發生了變化。現在整個包名都是小寫的)。由于決定創建一個名為`util`的庫,我可以進一步地分割它,所以最后得到的包名如下: ``` package com.bruceeckel.util; ``` 現在,可將這個包名作為下述兩個文件的“命名空間”使用: ``` //: Vector.java // Creating a package package com.bruceeckel.util; public class Vector { public Vector() { System.out.println( "com.bruceeckel.util.Vector"); } } ///:~ ``` 創建自己的包時,要求`package`語句必須是文件中的第一個“非注釋”代碼。第二個文件表面看起來是類似的: ``` //: List.java // Creating a package package com.bruceeckel.util; public class List { public List() { System.out.println( "com.bruceeckel.util.List"); } } ///:~ ``` 這兩個文件都置于我自己系統的一個子目錄中: ``` C:\DOC\JavaT\com\bruceeckel\util ``` 若通過它往回走,就會發現包名`com.bruceeckel.uti`l,但路徑的第一部分又是什么呢?這是由`CLASSPATH`環境變量決定的。在我的機器上,它是: ``` CLASSPATH=.;D:\JAVA\LIB;C:\DOC\JavaT ``` 可以看出,`CLASSPATH`里能包含大量備用的搜索路徑。然而,使用JAR文件時要注意一個問題:必須將JAR文件的名字置于類路徑里,而不僅僅是它所在的路徑。所以對一個名為`grape.jar`的JAR文件來說,我們的類路徑需要包括: ``` CLASSPATH=.;D:\JAVA\LIB;C:\flavors\grape.jar ``` 正確設置好類路徑后,可將下面這個文件置于任何目錄里(若在執行該程序時遇到麻煩,請參見第3章的3.1.2小節“賦值”): ``` //: LibTest.java // Uses the library package c05; import com.bruceeckel.util.*; public class LibTest { public static void main(String[] args) { Vector v = new Vector(); List l = new List(); } } ///:~ ``` 編譯器遇到`import`語句后,它會搜索由`CLASSPATH`指定的目錄,查找子目錄`com\bruceeckel\util`,然后查找名稱適當的已編譯文件(對于`Vector`是`Vector.class`,對于`List`則是`List.class`)。注意`Vector`和`List`內無論類還是需要的方法都必須設為`public`。 (1) 自動編譯 為導入的類首次創建一個對象時(或者訪問一個類的`static`成員時),編譯器會在適當的目錄里尋找同名的`.class`文件(所以如果創建類X的一個對象,就應該是`X.class`)。若只發現`X.class`,它就是必須使用的那一個類。然而,如果它在相同的目錄中還發現了一個`X.java`,編譯器就會比較兩個文件的日期標記。如果`X.java`比`X.class`新,就會自動編譯`X.java`,生成一個最新的`X.class`。 對于一個特定的類,或在與它同名的`.java`文件中沒有找到它,就會對那個類采取上述的處理。 (2) 沖突 若通過 `*` 導入了兩個庫,而且它們包括相同的名字,這時會出現什么情況呢?例如,假定一個程序使用了下述導入語句: ``` 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`的其他東西。 ## 5.1.2 自定義工具庫 掌握前述的知識后,接下來就可以開始創建自己的工具庫,以便減少或者完全消除重復的代碼。例如,可為`System.out.println()`創建一個別名,減少重復鍵入的代碼量。它可以是名為`tools`的一個包(`package`)的一部分: ``` //: P.java // The P.rint & P.rintln shorthand package com.bruceeckel.tools; public class P { public static void rint(Object obj) { System.out.print(obj); } public static void rint(String s) { System.out.print(s); } public static void rint(char[] s) { System.out.print(s); } public static void rint(char c) { System.out.print(c); } public static void rint(int i) { System.out.print(i); } public static void rint(long l) { System.out.print(l); } public static void rint(float f) { System.out.print(f); } public static void rint(double d) { System.out.print(d); } public static void rint(boolean b) { System.out.print(b); } public static void rintln() { System.out.println(); } public static void rintln(Object obj) { System.out.println(obj); } public static void rintln(String s) { System.out.println(s); } public static void rintln(char[] s) { System.out.println(s); } public static void rintln(char c) { System.out.println(c); } public static void rintln(int i) { System.out.println(i); } public static void rintln(long l) { System.out.println(l); } public static void rintln(float f) { System.out.println(f); } public static void rintln(double d) { System.out.println(d); } public static void rintln(boolean b) { System.out.println(b); } } ///:~ ``` 所有不同的數據類型現在都可以在一個新行輸出(`P.rintln()`),或者不在一個新行輸出(`P.rint()`)。 大家可能會猜想這個文件所在的目錄必須從某個`CLASSPATH`位置開始,然后繼續`com/bruceeckel/tools`。編譯完畢后,利用一個`import`語句,即可在自己系統的任何地方使用`P.class`文件。如下所示: ``` ToolTest.java ``` 所以從現在開始,無論什么時候只要做出了一個有用的新工具,就可將其加入`tools`目錄(或者自己的個人`util`或`tools`目錄)。 (1) `CLASSPATH`的陷阱 `P.java`文件存在一個非常有趣的陷阱。特別是對于早期的Java實現方案來說,類路徑的正確設定通常都是很困難的一項工作。編寫這本書的時候,我引入了`P.java`文件,它最初看起來似乎工作很正常。但在某些情況下,卻開始出現中斷。在很長的時間里,我都確信這是Java或其他什么在實現時一個錯誤。但最后,我終于發現在一個地方引入了一個程序(即第17章要說明的`CodePackager.java`),它使用了一個不同的類`P`。由于它作為一個工具使用,所以有時候會進入類路徑里;另一些時候則不會這樣。但只要它進入類路徑,那么假若執行的程序需要尋找`com.bruceeckel.tools`中的類,Java首先發現的就是`CodePackager.java`中的`P`。此時,編譯器會報告一個特定的方法沒有找到。這當然是非常令人頭疼的,因為我們在前面的類`P`里明明看到了這個方法,而且根本沒有更多的診斷報告可為我們提供一條線索,讓我們知道找到的是一個完全不同的類(那甚至不是`public`的)。 乍一看來,這似乎是編譯器的一個錯誤,但假若考察`import`語句,就會發現它只是說:“在這里可能發現了`P`”。然而,我們假定的是編譯器搜索自己類路徑的任何地方,所以一旦它發現一個`P`,就會使用它;若在搜索過程中發現了“錯誤的”一個,它就會停止搜索。這與我們在前面表述的稍微有些區別,因為存在一些討厭的類,它們都位于包內。而這里有一個不在包內的P,但仍可在常規的類路徑搜索過程中找到。 如果您遇到象這樣的情況,請務必保證對于類路徑的每個地方,每個名字都僅存在一個類。 ## 5.1.3 利用導入改變行為 Java已取消的一種特性是C的“條件編譯”,它允許我們改變參數,獲得不同的行為,同時不改變其他任何代碼。Java之所以拋棄了這一特性,可能是由于該特性經常在C里用于解決跨平臺問題:代碼的不同部分根據具體的平臺進行編譯,否則不能在特定的平臺上運行。由于Java的設計思想是成為一種自動跨平臺的語言,所以這種特性是沒有必要的。 然而,條件編譯還有另一些非常有價值的用途。一種很常見的用途就是調試代碼。調試特性可在開發過程中使用,但在發行的產品中卻無此功能。Alen Holub(`www.holub.com`)提出了利用包(`package`)來模仿條件編譯的概念。根據這一概念,它創建了C“斷定機制”一個非常有用的Java版本。之所以叫作“斷定機制”,是由于我們可以說“它應該為真”或者“它應該為假”。如果語句不同意你的斷定,就可以發現相關的情況。這種工具在調試過程中是特別有用的。 可用下面這個類進行程序調試: ``` //: Assert.java // Assertion tool for debugging 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); } } ///:~ ``` 這個類只是簡單地封裝了布爾測試。如果失敗,就顯示出出錯消息。在第9章,大家還會學習一個更高級的錯誤控制工具,名為“異常控制”。但在目前這種情況下,`perr()`方法已經可以很好地工作。 如果想使用這個類,可在自己的程序中加入下面這一行: ``` import com.bruceeckel.tools.debug.*; ``` 如欲清除斷定機制,以便自己能發行最終的代碼,我們創建了第二個`Assert`類,但卻是在一個不同的包里: ``` //: Assert.java // Turning off the assertion output // so you can ship the program. 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) {} } ///:~ ``` 現在,假如將前一個`import`語句變成下面這個樣子: ``` import com.bruceeckel.tools.*; ``` 程序便不再顯示出斷言。下面是個例子: ``` //: TestAssert.java // Demonstrating the assertion tool package c05; // Comment the following, and uncomment the // subsequent line to change assertion behavior: import com.bruceeckel.tools.debug.*; // import com.bruceeckel.tools.*; public class TestAssert { public static void main(String[] args) { Assert.is_true((2 + 2) == 5); Assert.is_false((1 + 1) == 2); Assert.is_true((2 + 2) == 5, "2 + 2 == 5"); Assert.is_false((1 + 1) == 2, "1 +1 != 2"); } } ///:~ ``` 通過改變導入的`package`,我們可將自己的代碼從調試版本變成最終的發行版本。這種技術可應用于任何種類的條件代碼。 ## 5.1.4 包的停用 大家應注意這樣一個問題:每次創建一個包后,都在為包取名時間接地指定了一個目錄結構。這個包必須存在(駐留)于由它的名字規定的目錄內。而且這個目錄必須能從`CLASSPATH`開始搜索并發現。最開始的時候,`package`關鍵字的運用可能會令人迷惑,因為除非堅持遵守根據目錄路徑指定包名的規則,否則就會在運行期獲得大量莫名其妙的消息,指出找不到一個特定的類——即使那個類明明就在相同的目錄中。若得到象這樣的一條消息,請試著將`package`語句作為注釋標記出去。如果這樣做行得通,就可知道問題到底出在哪兒。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看