第六章 訪問權(quán)限控制

訪問控制(或隱藏具體實(shí)現(xiàn))與“最初的實(shí)現(xiàn)并不恰當(dāng)”有關(guān)。

所有優(yōu)秀的作者,又是哪些編寫軟件的程序猿,都清楚其著作的某些部分直至重新創(chuàng)作的時(shí)候才會(huì)變得完美,有時(shí)甚至要反復(fù)重寫多次。如果你把一個(gè)代碼段放到某個(gè)位置,等過一會(huì)兒回頭再看時(shí),有可能發(fā)現(xiàn)有更好的方式去實(shí)現(xiàn)相同的功能。這正是重構(gòu)的原動(dòng)力之一,重構(gòu)既重寫代碼,以使它更可讀,更易理解,并因此更具可維護(hù)性。

但是,在這種修改和完善代碼的愿望之下,也存在著巨大的壓力。通??倳?huì)有一些消費(fèi)者(客戶端程序猿)需要你的代碼在某些方面保持不變。因此你想改變代碼,而他們卻想讓代碼保持不變。由此而產(chǎn)生在面向?qū)ο笤O(shè)計(jì)中需要考慮的一個(gè)基本問題:“如何把變動(dòng)的事物與保持不變的事物區(qū)分開來”。

這對(duì)類庫(library)而言尤為重要。該類庫的的消費(fèi)者必須依賴他所使用的那部分類庫,并且能夠知道如果類庫出現(xiàn)了新版本,他們并不需要改寫代碼。從另一方面來說,類庫的開發(fā)者必須有權(quán)限進(jìn)行修改和改進(jìn),并確??蛻舳舜a不會(huì)因?yàn)檫@些改動(dòng)而受到影響。

這個(gè)目標(biāo)可以通過約定來達(dá)成。例如,類庫開發(fā)者必須同意在改動(dòng)類庫是不得刪除任何現(xiàn)有的方法,因?yàn)槟窍鄷?huì)破壞客戶端的代碼。但是,與之相反的情況會(huì)更加棘手。在有域(既數(shù)據(jù)成員)存在的情況下,類庫的開發(fā)者怎樣才能知道究竟哪些域已經(jīng)被客戶端所調(diào)用了呢?這對(duì)于方法僅為類的實(shí)現(xiàn)的一部分,因此并不想讓客戶端程序猿直接使用的情況來說同樣如此。如果程序開發(fā)者想要移除舊的實(shí)現(xiàn)而要添加新的實(shí)現(xiàn)時(shí),結(jié)果將會(huì)怎樣呢?改動(dòng)任何一個(gè)成員都有可能破快客戶端程序猿的代碼。于是類庫開發(fā)者會(huì)手腳被縛,無法對(duì)任何事物進(jìn)行改動(dòng)。

對(duì)于解決這一問題,Java提供了訪問權(quán)限修飾詞,以供類庫開發(fā)人員向客戶端程序猿指明哪些是可用的,哪些是不可用的。訪問權(quán)限控制的等級(jí),從最大權(quán)限到最小權(quán)限依次是:publi, protected, 包訪問權(quán)限(沒有關(guān)鍵詞),和private。根據(jù)前述內(nèi)容,讀者可能會(huì)認(rèn)為,作為一個(gè)類庫設(shè)計(jì)者,你會(huì)盡可能將一切方法都定義為private,而僅向客戶端程序猿公開你愿意讓他們使用的方法。這樣做是完全正確的,盡管對(duì)于哪些經(jīng)常使用別的語言(特別是C語言)編寫程序并在訪問事物時(shí)不受任何限制的人來說,這與他們的直覺相違背。到了本章末,讀者將會(huì)信服Java對(duì)于訪問控制的價(jià)值。

不過, 構(gòu)件類庫的概念以及對(duì)于誰有權(quán)取用該類庫的控制問題都還是不完善的。其中仍舊存在著如何將構(gòu)件捆綁到一個(gè)內(nèi)聚的類庫單元的問題。對(duì)于這一點(diǎn),Java用關(guān)鍵詞package 加以控制,而訪問權(quán)限修飾詞會(huì)因類是存在于相同的包,還是存在一個(gè)單獨(dú)的包而受到影響。為此,要開始學(xué)習(xí)本章,首先要學(xué)習(xí)如果將類庫構(gòu)件置于包中,然后就會(huì)理解權(quán)限修飾詞的全部含義。

6.1 包:庫單元

包內(nèi)包含一組類,他們?cè)趩我坏拿挚臻g之下被阻止在了一起。
例如,在Java的標(biāo)準(zhǔn)發(fā)布中有一個(gè)工具庫,他被住址在java.util名字空間之下。java.util中有一個(gè)叫做ArrayList的類,使用ArrayList的一中方式是使用其全名java.util.ArrayList來指定。

public class FullQualification {
    public static void main(String[] args) {
        java.util.ArrayList list = new java.util.ArrayList()
    }
}

這樣就使程序變的冗長(zhǎng)了,因此你可能想轉(zhuǎn)而使用import關(guān)鍵字。如果你想導(dǎo)入單個(gè)類,可以在import 語句中命中該類:

import java.util.ArrayList;
public class FullQualification {
    public static void main(String[] args) {
        ArrayList list = new ArrayList()
    }
}

現(xiàn)在就可以限定地使用ArrayList了。但是,這樣做java.util中的其他類仍舊是都不可用的。想要導(dǎo)入其中的所有類,只需要使用“”,就像本書剩余部分的實(shí)例中所看到的那樣:
import java.util.

我們之所以想要導(dǎo)入,就是要提供一個(gè)管理名字空間的機(jī)制。所有類成員的名稱都是彼此隔離的。A類中的方法f()與B類中的具有相同特征標(biāo)識(shí)(參數(shù)列表)方法f()不會(huì)彼此沖突。但是如果類名稱沖突該怎么辦呢?比如你編寫了一個(gè)Stack類并安裝到了一臺(tái)機(jī)器上,而該機(jī)器上已經(jīng)有了一個(gè)別人編寫的Stack類,我們?cè)撊绾谓鉀Q呢?由于名字之間的沖突,在Java中對(duì)名稱空間進(jìn)行完全控制并為每個(gè)類創(chuàng)建唯一標(biāo)識(shí)符組合就成為了非常重要的事情。

到目前為止,書中大多數(shù)的實(shí)例都存在于單一文件之中,并專為本地使用(local use)而設(shè)計(jì),而并未受到包名的干擾。這些事例實(shí)際已經(jīng)位于包中了;即未命名包,或稱為默認(rèn)包,這當(dāng)然也是一種選擇,而且為了簡(jiǎn)單起見,在本書其他部分都盡可能使用此方法。不過如果你準(zhǔn)備編寫對(duì)一臺(tái)機(jī)器上共存的其他java程序友好的類庫或程序的話,就需要考慮如何防止名稱之間的沖突問題。

當(dāng)編寫一個(gè)Java源碼文件時(shí),此文件通常稱為編譯單元(有時(shí)候也被稱為轉(zhuǎn)移單元)。每個(gè)編譯單元必須有一個(gè)后綴名.java,而在編譯單元內(nèi)則可以有一個(gè)public類,該類的名稱必須與文件的名稱相同。每個(gè)編譯單元只能有一個(gè)public類,否則編譯器不會(huì)接受。如果在編譯單元之中還有額外的類的話,那么在包之外的世界是無法看見這些類的。這正是因?yàn)樗麄儾皇莗ublic類,而且他們主要用來為public配提供支持。

6.11 代碼組織

當(dāng)編譯一個(gè).java文件時(shí),在.java文件的每個(gè)類都會(huì)有一個(gè)輸出文件,而該輸出文件的文件名與.java文件里的每個(gè)類的名稱相同,只是多了一個(gè)后綴名.class。因此,在編譯少量.java文件之后會(huì)得到大量的.class文件。如果用編譯型語言編寫過程序,那么對(duì)于編譯器產(chǎn)生一個(gè)中間文件(通常是一個(gè)obj文件),然后再與鏈接器(用以創(chuàng)建一個(gè)可執(zhí)行文件)或類庫產(chǎn)生器(librarian,用以常見一個(gè)類庫)產(chǎn)生的其他同類文件捆綁在一起的情況,可能早已司空見慣。但這并不是java的工作方式。java的可運(yùn)行程序是一組可以打包并壓縮為一個(gè)jar文檔文件的.class文件。java解釋器負(fù)責(zé)這些文件的查找、裝載和解釋。類庫實(shí)際上是一組類文件。其中每個(gè)文件都有一個(gè)public類,以及任意數(shù)量的非public類。因此每一個(gè)文件都有一個(gè)構(gòu)件。如果希望這些構(gòu)件從屬于一個(gè)群組就可以使用關(guān)鍵詞package。

如果使用package語句,他必須是文件中除注釋以外的第一句程序代碼。在文件起始處寫: package access;
就表明你在聲明該編譯單元是名為access的類庫的一部分。或者換種說法,你正在聲明該編譯單元中public 類名稱是位于access名稱的保護(hù)傘下。任何想要使用該名稱的人都必須使用前面給出的選擇:指定全名或者與access結(jié)合使用關(guān)鍵字import。(請(qǐng)注意。java的包名規(guī)則全部使用小寫字母,包括中間的字也是如此。)

例如,假設(shè)文件的名稱是MyClass.java,這就意味著在該文件中有且只有一個(gè)public類,該類的名稱必須是MyClass:

package access.mypackage;
public class MyClass {
    //…………….
}

現(xiàn)在如果有人想用MyClass或者是access中任何其他public類,就必須使用關(guān)鍵字import 來使access中的名稱可用。另一個(gè)選擇時(shí)給出完整名稱。

//: access/QualifiedMyClass.java

public class QualifiedMyClass {
  public static void main(String[] args) {
    access.mypackage.MyClass m =
      new access.mypackage.MyClass();
  }
} ///:~
關(guān)鍵字import可使之更加簡(jiǎn)潔:
//: access/ImportedMyClass.java
import access.mypackage.*;

public class ImportedMyClass {
  public static void main(String[] args) {
    MyClass m = new MyClass();
  }
} ///:~

身為一個(gè)類庫設(shè)計(jì)員,很有必要牢記: package和import關(guān)鍵字允許你做的,是將單一的全局名字空間分隔開,使得無論多少人使用Internet以及Java開始編寫類,都不會(huì)出現(xiàn)名字沖突問題。

6.1.2 創(chuàng)建獨(dú)一無二的包名

讀者也許會(huì)發(fā)現(xiàn),既然一個(gè)包從未真正將打包的東西包裝成一個(gè)單一的文件,并且一個(gè)包可以由許多.class文件構(gòu)成,那么情況就有點(diǎn)復(fù)雜了。為了避免這種情況的發(fā)生,一中合乎邏輯的做法就是將特定包的所有.calss文件都置于一個(gè)目錄下。也就是說,利用操作系統(tǒng)的層次化文件結(jié)構(gòu)來解決這一問題。這是Java解決混亂問題的一種方式,讀者還會(huì)在我們介紹jar工具的時(shí)候看到另一種方式。

將所有的文件收入一個(gè)子目錄還可以解決另外兩個(gè)問題:怎樣創(chuàng)建獨(dú)一無二的名稱以及怎樣查找有可能隱藏于目錄結(jié)構(gòu)中某處的類。這些任務(wù)是通過將.class文件所在的路徑位置編輯成package的名稱來實(shí)現(xiàn)的。按照慣例,package名稱的第一部分是類的創(chuàng)建者的反順序的internet域名。如果你遵照慣例,Internet域名應(yīng)該是獨(dú)一無二的,因此你的package名稱也將是獨(dú)一無二的,也就不會(huì)出現(xiàn)名稱沖突的問題了。當(dāng)然如果你沒有自己的域名,你就得構(gòu)造一組不大可能與他人重復(fù)的組合來創(chuàng)建獨(dú)一無二的package名稱。如果你打算發(fā)布你的java程序代碼,稍微花點(diǎn)力氣取得一個(gè)域名還是很有必要的。

此技巧的第二部分是把package名稱分解成你機(jī)器上的一個(gè)目錄。所以當(dāng)java程序運(yùn)行并且需要加載.class文件的時(shí)候,他就可以確定.class文件在目錄所處的位置。

Java解釋器的運(yùn)行過程如下:首先找出環(huán)境變量CLASSPATH。CLASSPATH包含一個(gè)或多個(gè)目錄,用作查找.class文件的根目錄。從根目錄開始,解釋器獲取包名稱并將每個(gè)句點(diǎn)替換成反斜杠,以從CLASSPATH根中產(chǎn)生一個(gè)路徑名稱(于是,package foo.bar.baz 就會(huì)變成foo\bar\baz或者foo/bar/baz或其他,這一切取決于操作系統(tǒng))。得到的路徑會(huì)與CLASSPATH中的一個(gè)或各個(gè)不同的項(xiàng)相連接,解釋器就在這些目錄中查找你要?jiǎng)?chuàng)建的類名稱相關(guān)的.class文件。(解釋器還會(huì)去查找某些設(shè)計(jì)Java解釋器所在目錄的標(biāo)準(zhǔn)目錄)。

為了理解這一點(diǎn),以我的域名MindView為例,把他的順序倒過來,并且全部轉(zhuǎn)為小寫,net.mindview就成了我所創(chuàng)建的類的獨(dú)一無二的全局名稱。若我決定在創(chuàng)建一個(gè)名為simple的類庫,我就可以以該名稱進(jìn)一步細(xì)分,于是我可以得到一個(gè)包的名稱如下:
package net.mindview.simple
現(xiàn)在,這個(gè)包名稱就可以用作下面兩個(gè)文件的保護(hù)傘了:

//: net/mindview/simple/Vector.java
// Creating a package.
package net.mindview.simple;

public class Vector {
  public Vector() {
    System.out.println("net.mindview.simple.Vector");
  }
} ///:~

如前所述,package語句必須是文件的第一行非注釋程序代碼。第二個(gè)文件看起來也是相似:

//: net/mindview/simple/List.java
// Creating a package.
package net.mindview.simple;

public class List {
  public List() {
    System.out.println("net.mindview.simple.List");
  }
} ///:~

這兩個(gè)文件均被置于我的文件系統(tǒng)的子目錄下:
C:\Doc\JavaT\net\mindview\simple
如果沿此路徑往回看可以看到包的名稱net.mindview.simple, 但此路徑的第一部分怎么辦呢?它將由環(huán)境變量CLASSPATH關(guān)照,在我的機(jī)器上是:
CLASSPATH=.;D:\JAVA\LIB;C:\DOC\JavaT
可以看到CLASSPATH可以包含多個(gè)可供選擇的查詢路徑。
但是在使用JAR文件時(shí)會(huì)出現(xiàn)一點(diǎn)變化。必須在類路徑中將JAR文件的實(shí)際名稱寫清楚,而不僅僅是指明它所在的位置的目錄。因此,對(duì)一個(gè)名為grape.jar的JAR文件。類路徑應(yīng)該包括:
CLASSPATH=.;D:\JAVA\LIB;C:\flavors\grape.jar
一旦類路徑得以正確建立,下面的文件就可以放于任何目錄之下:

//: access/LibTest.java
// Uses the library.
import net.mindview.simple.*;

public class LibTest {
  public static void main(String[] args) {
    Vector v = new Vector();
    List l = new List();
  }
} /* Output:
net.mindview.simple.Vector
net.mindview.simple.List
*///:~

當(dāng)編譯器遇到simple庫的import 語句時(shí),就開始在CLASSPATH所指定的目錄中查找,查找子目錄net\mindview\simple,然互從已編譯的文件中找出名稱相符者。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,641評(píng)論 19 139
  • public > protected > 包訪問權(quán)限 > private 包:庫單元 包:一組類的集合。通過imp...
    老茂在北京閱讀 210評(píng)論 0 0
  • 宮花淺淺 目錄 簡(jiǎn)書連載風(fēng)云錄 上一回 第二章(7) “咳咳咳……是你?”蘇淺淺睜大眼睛看著面前的人。 “噓……”...
    莫楠Emily閱讀 938評(píng)論 5 9
  • 時(shí)間下午6點(diǎn)零7分,我在家樓下的舌尖尖牛肉面吃飯,剛端著面坐下聽到了推門聲,習(xí)慣性的向門口望去。只見一個(gè)姑娘進(jìn)門,...
    自問君閱讀 339評(píng)論 0 0
  • 智能時(shí)代,企業(yè)所處的商業(yè)環(huán)境發(fā)生了根本變化,部署更加先進(jìn)的ERP軟件,成為現(xiàn)代企業(yè)謀求生存和發(fā)展的重要途徑,也是全...
    風(fēng)一樣的人123閱讀 406評(píng)論 0 2

友情鏈接更多精彩內(nèi)容