當設計面向對象時,壹個基本的考慮是:如何將已經改變的東西與保持不變的東西分開
這對圖書館尤其重要。該庫的用戶(客戶端程序員)必須能夠依賴他們使用的部分,並且知道壹旦新版本的庫出來,他們不需要重寫代碼。相反,庫的創建者必須能夠自由地修改和改進,同時確保客戶端程序員的代碼不會受到那些更改的影響。
為了實現這個目標,需要遵守壹定的協議或規則。例如,當庫程序員修改庫中的壹個類時,他必須確保現有的方法沒有被刪除,因為這樣做會導致客戶端程序員的代碼中出現斷點。然而,相反的情況是痛苦的。對於壹個數據成員,庫的創建者如何知道客戶端程序員訪問過哪些數據成員?如果方法只屬於壹個類的壹部分,並且不壹定被客戶端程序員直接使用,這種令人痛苦的情況也是真實的。如果庫的創建者想要刪除壹個舊的實現並放入新的代碼,該怎麽辦?對這些成員的任何更改都可能會中斷客戶端程序員的代碼。所以庫創建者處於壹個尷尬的位置,似乎根本無法移動。
為了解決這個問題,Java引入了“訪問指示器”的概念,它允許庫創建者聲明什麽能被客戶機程序員使用,什麽不能被客戶機程序員使用。這種訪問控制的級別介於“最大訪問”和“最小訪問”之間,分別包括:公共、“友好”(無關鍵字)、受保護和私有。根據上壹段的描述,妳可能已經得出結論,作為壹個庫設計者,壹切都應該盡可能地保持“私有”,只顯示那些妳希望客戶端程序員使用的方法。這種想法是完全正確的,雖然對於那些用其他語言(尤其是C)編程的人來說有點反直覺,他們習慣於不受任何限制地訪問壹切。到本章結束時,妳應該能夠深刻體會到Java訪問控制的價值。
然而,組件庫和控制誰可以訪問該庫的組件的概念仍然不完整。還有壹個問題:如何將組件綁定到壹個統壹的庫單元中。這是通過Java的package關鍵字實現的,訪問指示器受類是在同壹個包中還是在不同的包中的影響。所以在本章開始,我們必須首先學習如何將庫組件放入包中。只有這樣,我們才能理解訪問指示器的全部含義。
5.1包:庫單元
當我們用import關鍵字導入壹個完整的庫時,會得到壹個“包”。例如:
導入Java . util . *;
它的功能是導入壹個完整的實用程序庫,這是標準Java開發工具包的壹部分。由於Vector位於java.util中,現在要麽指定全名“java.util.Vector”(可以省略import語句),要麽只指定壹個“Vector”(因為import是默認的)。
如果要導入單個類,可以在import語句中指定該類的名稱:
導入Java . util . vector;
現在,我們可以自由地使用矢量。但是,java.util中的任何其他類仍然不可用。
這種導入的原因是為了提供壹種特殊的機制來管理“名稱空間”。我們班所有成員的名字都會相互隔離。類A中的方法f()與類B中具有相同“簽名”(參數列表)的方法f()不沖突。但是類名會沖突嗎?假設您創建了壹個stack類,並將其安裝在已經有壹個stack類(由其他人編寫)的機器上。會發生什麽?對於互聯網上的Java應用,在用戶不知情的情況下會出現這種情況,因為運行壹個Java程序時類會自動下載。
正是因為名字的潛在沖突,所以在Java中完全控制命名空間,創建壹個完全唯壹的名字就顯得尤為必要,不管互聯網上存在什麽樣的限制。
到目前為止,本書中的大多數例子都只存在於單個文件中,並且它們是為本地(local)使用而設計的,與包名沒有沖突(在這種情況下,類名放在“默認包”中)。這是壹種有效的方法,考慮到問題的簡化,本書其余部分將盡可能采用。然而,如果妳計劃創建壹個“互聯網友好的”或“適合互聯網的”程序,妳必須考慮如何防止類名的重復。
為Java創建源文件時,通常稱為“編輯單元”(有時稱為“翻譯單元”)。每個編譯單元必須有壹個以結尾的名稱。java。在編譯單元內部,可以有壹個公共類,它必須與文件同名(包括大小寫,但不包括。java文件擴展名)。如果不這樣做,編譯器會報告壹個錯誤。每個編譯單元中只能有壹個公共類(同樣,否則編譯器會報錯)。該編譯單元的其余類(如果有的話)可以對包外的世界隱藏起來,因為它們不是“公共的”,而是由主公共類的“支持”類組成的。
當編譯壹個java文件時,我們會得到壹個同名的輸出文件;但是對於。java文件,它們有壹個. class擴展名。因此,我們最終可能會得到大量的。壹小部分。java文件。如果妳以前用匯編語言寫過壹個程序,妳可能習慣於編譯器把壹個過渡形式(通常是壹個。obj文件),然後用鏈接器和其他東西打包(生成可執行文件),或者用庫打包(生成庫)。但這不是Java的工作方式。壹個有效的程序是壹系列的。可以封裝和壓縮到壹個jar文件中的類文件(使用Java 1.1提供的JAR工具)。Java解釋器負責查找、加載和解釋這些文件(註1)。
①: Java不強迫妳使用解釋器。壹些帶有本機代碼的Java編譯器可以生成單獨的可執行文件。
“庫”也是由壹系列的類文件組成的。每個文件都有壹個公共類(不強制使用公共類,但這是最典型的情況),所以每個文件都有壹個組件。如果妳想總結所有這些組成部分(它們是分開的。java和。類文件),那麽package關鍵字就可以發揮作用了)。
如果在文件開頭使用以下代碼:
打包mypackage
那麽package語句必須作為文件的第壹個非註釋語句出現。這個語句的作用是指出這個編譯單元屬於壹個名為mypackage的庫的壹部分。或者換句話說,它顯示這個編譯單元中的公共類名在mypackage這個名稱下。如果其他人想要使用這個名稱,要麽指出全名,要麽將import關鍵字與mypackage結合使用(使用上面給出的選項)。註意,根據Java包(封裝)的約定,名稱中的所有字母都要小寫,即使是那些中間的單詞。
例如,假設文件名是MyClass.java。這意味著該文件中有且只有壹個公共類。並且該類的名稱必須是MyClass(包括case):
打包mypackage
公共類MyClass {
// .。。
現在,如果有人想要使用mypackage中的MyClass或任何其他公共類,他們必須使用import關鍵字來激活mypackage中的名稱,以便他們可以使用它們。另壹種方法是指定全名:
我的包裹。MyClass m =新的mypackage。my class();
import關鍵字可以使它變得更簡單:
導入我的包。*;
// .。。
my class m = new my class();
作為壹個庫設計者,我們必須記住,package和import關鍵字允許我們做的是拆分單個全局名稱空間,以確保我們不會遇到名稱沖突——不管有多少人使用互聯網或用Java編寫自己的類。
5.1.1創建唯壹的包名。
您可能已經註意到這樣壹個事實,由於壹個包從來沒有真正“封裝”到壹個文件中,所以它可以由多個文件組成。類文件,所以情況可能有點混亂。為了避免這個問題,最合理的方法是將所有的。特定包使用的類文件放在壹個目錄中。換句話說,我們應該使用操作系統的分層文件結構來避免混淆。這正是Java采用的方法。
它還解決了另外兩個問題:創建唯壹的包名和找出那些可能隱藏在目錄結構中的類。正如我們在第2章中提到的,為了實現這個目標,我們需要對。類文件轉換成包的名稱。但是按照約定,編譯器強制包名的第壹部分是類創建者的Internet域名。因為互聯網域名肯定是唯壹的(由InterNIC-note ②保證,它控制域名的分布),如果遵循這個協議,包的名字肯定不會重復,所以永遠不會遇到名字沖突的問題。換句話說,除非妳把域名轉讓給別人,對方按照相同的路徑名寫Java代碼,否則永遠不會發生名稱沖突。當然,如果妳沒有自己的域名,妳必須創建壹個非常不常用的包名(比如妳自己的英文名),以便盡可能創建壹個唯壹的包名。如果決定發布自己的Java代碼,強烈建議申請自己的域名,很便宜。
②:ftp://ftp.internic.net
這項技術的另壹部分是將包名解析到您自己機器上的壹個目錄中。這樣,當Java程序運行並需要加載。類文件(這是動態的,當程序需要創建壹個屬於該類的對象或第壹次訪問該類的靜態成員時),它可以找到。類文件駐留。
Java解釋器的工作過程如下:首先,它找到環境變量CLASSPATH(當機器中安裝了Java或具有Java解釋能力的工具,如瀏覽器時,由操作系統設置)。類路徑包含壹個或多個目錄,這些目錄被用作特殊的“根”,搜索從這些目錄開始。類文件被啟動。從那個根開始,解釋器會尋找包名,用斜杠代替每個點(句號),從而生成壹個從類路徑根開始的路徑名(所以包foo.bar.baz會變成foo\bar\baz或者foo.bar.baz是正斜杠還是反斜杠由操作系統決定)。然後將它們連接在壹起,成為類路徑中的各種條目(entries)。搜索的時候。以後的類文件,可以從這些地方開始,找到要創建的類名對應的名稱。此外,它還會搜索壹些標準目錄——這些目錄與Java解釋器駐留的位置相關。
為了進壹步理解這個問題,我們以我自己的域名為例。這是bruceeckel.com。反過來之後,com.bruceeckel為我的類創建了壹個唯壹的全局名(之前Java包裏com、edu、org、net之類的擴展名都是大寫的,但是從Java 1.2開始這種情況發生了變化。現在整個包名都是小寫)。因為我決定創建壹個名為util的庫,所以我可以進壹步劃分它,所以最終的包名如下:
包com . Bruce eckel . util;