Java漫談(四)

命名空間

包名

????Java代碼里面的類寫多了傍衡,自然會(huì)遇到重名的問題螺垢,尤其是涉及到多人協(xié)作的時(shí)候尖昏。比如,兩個(gè)人都想給自己的類命名為“Apple”聚凹,那么第三個(gè)人調(diào)用“Apple”這個(gè)類的時(shí)候割坠,到底調(diào)的是哪個(gè)呢?
????這就是命名沖突妒牙,Java給出的解決方案是引入包名彼哼,也就給類名加個(gè)前綴,如:com.Demo2湘今。Demo2類的包名就是com敢朱,類名和包名之間用點(diǎn)號隔開。這時(shí)候“類名”就有了兩個(gè)不同版本的叫法:全限定類名和
非限定類名摩瞎。前者指的是加上前綴之后的全名拴签,后者就是平時(shí)所說的類名,也就是class關(guān)鍵字后面的名字旗们。語法上蚓哩,使用package關(guān)鍵字來單獨(dú)聲明包名,而不是將全限定類名放在class關(guān)鍵后面上渴。如下所示:

package com;

class Demo {}


????那么岸梨,類Demo2的全限定類名就是com.Demo2。有了前綴稠氮,就不怕沖突了曹阔,兩個(gè)重名的類,只要他們的前綴不一樣括袒,也就是全限定類名不一樣次兆,就不會(huì)引起沖突。當(dāng)然锹锰,引用的時(shí)候芥炭,需要寫入全限定類名,而不是之前的“簡稱”恃慧。前綴园蝠,也就是包名,給了類一個(gè)安全的命名空間痢士,只要在這個(gè)空間內(nèi)沒有重名就好彪薛,其余的事情讓包名去考慮茂装。在java的規(guī)范中,包名需要全部小寫善延。

包的層次

????如果包名也重名了怎么半少态?當(dāng)然,理論上講易遣,包名可以做到不重名彼妻。26個(gè)英文字母隨意排列組合,而且長度也可以無限擴(kuò)大豆茫,怎么著都能組裝出一個(gè)不同的包名侨歉。然而可以想象的是,隨著類的無限增加揩魂,這個(gè)包名也會(huì)跟著不斷增長幽邓,而且隨意排列的名稱也越來越?jīng)]有意義。這顯然不是我們想要的火脉,于是設(shè)計(jì)者給包名添加了層級結(jié)構(gòu)牵舵,類似于文件系統(tǒng)的目錄樹,包下面還可以有子包倦挂,各級之間的包名也用點(diǎn)號隔開棋枕。這樣以來,問題也就解決了:當(dāng)給一個(gè)類起了一個(gè)喜歡的包名妒峦,發(fā)現(xiàn)被別人占用了重斑,那么給它加一個(gè)“父包名”;如果“父包名”也沖突肯骇,重復(fù)剛才的步驟窥浪,直到?jīng)]有沖突。實(shí)際上笛丙,設(shè)計(jì)者建議使用公司層級倒序的域名作為包名漾脂,域名在世界范圍內(nèi)幾乎是唯一的。

默認(rèn)權(quán)限

????默認(rèn)權(quán)限其實(shí)指的就是包權(quán)限胚鸯。當(dāng)聲明一個(gè)類時(shí)骨稿,若class關(guān)鍵字前沒有任何訪問權(quán)限修飾符,那么這個(gè)類默認(rèn)的可訪問權(quán)限就是包權(quán)限姜钳,即它只能被同一個(gè)包下的類訪問坦冠,不是同一個(gè)包的類無法訪
問到它。
將類Demo2的包名聲明為com2哥桥,如下:

package com2;

class Demo2 {
    public static int a = 9;

    public static void main(String[] args) {
        System.out.println(a);
    }
}

????參照上篇文章中的目錄樹結(jié)構(gòu)辙浑,src子目錄專門存放源代碼,而target目錄作為所有class文件的根目錄拟糕。先編譯Demo2:

?  show tree
.
├── src
│   ├── Demo1.java
│   └── Demo2.java
└── target

2 directories, 2 files
?  show javac -d target src/Demo2.java 
?  show tree
.
├── src
│   ├── Demo1.java
│   └── Demo2.java
└── target
    └── com2
        └── Demo2.class

3 directories, 3 files
?  show 

????可以看到判呕,編譯之后得到的class文件倦踢,其名稱只是類的“簡稱”,而非類的全限定名侠草;同時(shí)javac在target目錄下自動(dòng)生成了com2子目錄辱挥,對應(yīng)類Demo2的包名”咛椋可以想象般贼,如果Demo2的包名具有多級結(jié)構(gòu),那么在根目錄(即使用-d參數(shù)指定的class文件根目錄奥吩,或者默認(rèn)為當(dāng)前目錄)下也將生成與包名對應(yīng)的目錄樹。另外一種方案是蕊梧,直接在target目錄下生成以類的全限定名稱命名的class文件霞赫,而非建立與包名對應(yīng)的目錄樹——但是javac并沒有采用這種方案。
????接下來將類Demo1的包名設(shè)為com1肥矢,并且Demo1訪問了com2.Demo2的a屬性端衰,注意Demo1引用Demo2的時(shí)候,寫的是其全限定名甘改。

package com1;

class Demo1 {
    public static void main(String[] args) {
        System.out.println(com2.Demo2.a);
    }
}

編譯過程如下:

?  show javac -cp target/com2 -d target src/Demo1.java
src/Demo1.java:5: 錯(cuò)誤: 程序包c(diǎn)om2不存在
        System.out.println(com2.Demo2.a);
                               ^
1 個(gè)錯(cuò)誤

????程序包c(diǎn)om2顯然是存在的旅东,這里之所以報(bào)錯(cuò)是因?yàn)?cp指定的路徑有問題——實(shí)際上只需要指定class文件的根目錄就可以了,而由類的包名自動(dòng)生成的目錄樹十艾,javac會(huì)自動(dòng)根據(jù)包名去搜索抵代,不必指定⊥担看起
來與上篇文件說的“javac不會(huì)遞歸搜索子目錄”相矛盾荤牍,但這是由于那時(shí)尚未引入包機(jī)制導(dǎo)致的。重新編譯如下:

?  show javac -cp target -d target src/Demo1.java 
src/Demo1.java:5: 錯(cuò)誤: Demo2在com2中不是公共的; 無法從外部程序包中對其進(jìn)行訪問
        System.out.println(com2.Demo2.a);
                               ^
1 個(gè)錯(cuò)誤
?  show 

????終于出現(xiàn)了想要的錯(cuò)誤(汗庆冕,有點(diǎn)像設(shè)計(jì)劇情)——由于Demo2沒有和Demo1在同一個(gè)包下康吵,且Demo2沒有被public修飾,所以Demo1無法訪問到Demo2访递。
????想要讓Demo1編譯成功晦嵌,有兩個(gè)辦法:將二者的包名改為同一個(gè);或者將類Demo2的訪問權(quán)限改為public拷姿。如果Demo2是一個(gè)“私有類”惭载,即開發(fā)者不希望這個(gè)類被別人依賴或訪問,那么將其訪問權(quán)限限制在包
內(nèi)是個(gè)好辦法响巢,這也就意味著該類只提供“包內(nèi)服務(wù)”棕兼,不提供“公共服務(wù)”。反之抵乓,如果是開發(fā)者對外發(fā)布的接口伴挚,那么這個(gè)類必須設(shè)置為public靶衍,這樣才會(huì)被別人訪問到。

回答

????好茎芋,現(xiàn)在回答最初的那個(gè)問題:為什么源代碼文件要以public類的名字命名颅眶?
????還是為了自動(dòng)化編譯提高效率。public類是可以被任何類訪問的類田弥,那么當(dāng)javac因其他類依賴這個(gè)public類而要編譯它的時(shí)候涛酗,必然需要根據(jù)類名去尋找這個(gè)類的源代碼文件,所以源代碼文件必須要根據(jù)public類去命名偷厦。實(shí)際上商叹,只要某個(gè)類想要被javac自動(dòng)編譯,就必然需要單獨(dú)保存在以它自己的名字命名的源代碼文件中只泼,不管它是不是public類剖笙。雖然一個(gè)源代碼文件中可以出現(xiàn)多個(gè)類的聲明,但是只要有一個(gè)類被public修飾了请唱,當(dāng)其他類引用這個(gè)類的時(shí)候弥咪,就必須要保證javac可以根據(jù)類名找到這份源代碼。而同一份源代碼文件中十绑,非public類會(huì)因?yàn)閜ublic類的編譯而同時(shí)被編譯聚至。當(dāng)然,如果類A依賴了同包下的非public類B本橙,但是類B的源代碼文件是以同一份文件中public類C的名字命名的扳躬,那么類B將不會(huì)因?yàn)锳的依賴而被自動(dòng)編譯——除非類C因?yàn)槟撤N原因被編譯了。不過話說回來甚亭,既然想要類B被自動(dòng)編譯坦报,為啥不把它單獨(dú)存放在一個(gè)以它自己的名字命名的文件中呢?實(shí)際上狂鞋,如果一
份源代碼文件中同時(shí)出現(xiàn)了public和非public類片择,那么一般情況下,這些非public類都只是為這一個(gè)public類服務(wù)的骚揍,設(shè)計(jì)者其實(shí)不希望這些非public類被這個(gè)public類以外的任何類訪問到——哪怕是在同一個(gè)包下的類字管。
????上面這個(gè)需求實(shí)際上使用內(nèi)部類可能更合適些,不知道是不是java的遺留問題信不,目前我在java類庫的一些源代碼文件確實(shí)發(fā)現(xiàn)了public類和非public共存的現(xiàn)象嘲叔。

包結(jié)構(gòu)與目錄

class文件目錄

????接上面,采用第二種辦法抽活,將類Demo2的訪問權(quán)限修改為public:

package com2;

public class Demo2 {
    public static int a = 9;

    public static void main(String[] args) {
        System.out.println(a);
    }
}

????繼續(xù)編譯:

?  show javac -d target src/Demo2.java 
?  show javac -cp target -d target src/Demo1.java 
?  show tree
.
├── src
│   ├── Demo1.java
│   └── Demo2.java
└── target
    ├── com1
    │   └── Demo1.class
    └── com2
        └── Demo2.class

4 directories, 4 files

????編譯成功硫戈,現(xiàn)在target目錄下有com1和com2兩個(gè)子目錄,分別存放Demo1.class和Demo2.class下硕。

源代碼目錄

????然而丁逝,Demo2.class是手動(dòng)編譯出來的汁胆,但是javac有自動(dòng)編譯依賴類的功能,為啥不用呢霜幼?

?  show rm -rf target/*.class
?  show tree
.
├── src
│   ├── Demo1.java
│   └── Demo2.java
└── target

2 directories, 2 files
?  show javac -sourcepath src -d target src/Demo1.java
src/Demo1.java:5: 錯(cuò)誤: 程序包c(diǎn)om2不存在
        System.out.println(com2.Demo2.a);
                               ^
1 個(gè)錯(cuò)誤

????看來包名影響的不光是class文件的目錄組織嫩码,源代碼也受到了影響。想要使用javac的自動(dòng)編譯功能罪既,源代碼的目錄結(jié)構(gòu)也需要按照包名去組織铸题,如下:

?  show tree
.
├── src
│   ├── com1
│   │   └── Demo1.java
│   └── com2
│       └── Demo2.java
└── target

4 directories, 2 files
?  show javac -sourcepath src -d target src/com1/Demo1.java
?  show tree
.
├── src
│   ├── com1
│   │   └── Demo1.java
│   └── com2
│       └── Demo2.java
└── target
    ├── com1
    │   └── Demo1.class
    └── com2
        └── Demo2.class

6 directories, 4 files

執(zhí)行

?  show java target/com1/Demo1
錯(cuò)誤: 找不到或無法加載主類 target.com1.Demo1
?  show java -cp target com1.Demo1
9
?  show cd target 
?  target java com1.Demo1 
9
?  target cd com1
?  com1 java Demo1 
錯(cuò)誤: 找不到或無法加載主類 Demo1

????上面的試錯(cuò)說明了兩件事:
????1. 不同于javac命令,使用java命令執(zhí)行一個(gè)類時(shí)琢感,不可以直接指定class文件的實(shí)際路徑丢间,只能寫全限定類名,java會(huì)自動(dòng)根據(jù)包名去實(shí)際路徑下尋找驹针;
????2. -classpath參數(shù)對于java命令是一樣的烘挫,用來指定class文件的根目錄(頂級包所在的目錄),并且java默認(rèn)的搜索路徑就是當(dāng)前目錄牌捷。

默認(rèn)包

????如果源碼文件中沒有使用package關(guān)鍵字來聲明該類的包名(語法上這是合法的),那么這個(gè)類就屬于默認(rèn)包涡驮。

與目錄無關(guān)

????默認(rèn)包下的所有類都是可以互相訪問的暗甥,不管它們有沒有實(shí)際上在同一個(gè)目錄下。上面兩個(gè)Demo類捉捅,去掉package相關(guān)的代碼后撤防,編譯過程如下:

?  show tree
.
├── src
│   ├── com1
│   │   └── Demo1.java
│   └── com2
│       └── Demo2.java
└── target
    ├── com1
    └── com2

6 directories, 2 files
?  show javac -d target/com2 src/com2/Demo2.java
?  show javac -cp target/com2 -d target/com1 src/com1/Demo1.java
?  show tree
.
├── src
│   ├── com1
│   │   └── Demo1.java
│   └── com2
│       └── Demo2.java
└── target
    ├── com1
    │   └── Demo1.class
    └── com2
        └── Demo2.class
│       └── Demo2.java
└── target
    ├── com1
    └── com2

6 directories, 2 files
?  show javac -d target/com2 src/com2/Demo2.java
?  show javac -cp target/com2 -d target/com1 src/com1/Demo1.java
?  show tree
.
├── src
│   ├── com1
│   │   └── Demo1.java
│   └── com2
│       └── Demo2.java
└── target
    ├── com1
    │   └── Demo1.class
    └── com2
        └── Demo2.class

6 directories, 4 files
?  show java -cp target/com1:target/com2 Demo1
9

????target下的com1和com2是故意保留的兩個(gè)目錄,分別存放編譯后的Demo1.class和Demo2.class棒口。類Demo1的編譯和執(zhí)行都成功了寄月,驗(yàn)證了上面的猜測。

引用默認(rèn)包的public類

????問題:如果想要引用默認(rèn)包中的public類无牵,該怎么做漾肮?代碼更改如下:

package com1;

class Demo1 {
    public static void main(String[] args) {
        System.out.println(Demo2.a);
    }
}
public class Demo2 {
    public static int a = 9;

    public static void main(String[] args) {
        System.out.println(a);
    }
}

????編譯:

?  show javac -d target/com2 src/com2/Demo2.java
?  show javac -cp target/com2 -d target/com1 src/com1/Demo1.java
src/com1/Demo1.java:5: 錯(cuò)誤: 找不到符號
        System.out.println(Demo2.a);
                           ^
  符號:   變量 Demo2
  位置: 類 Demo1
1 個(gè)錯(cuò)誤

????報(bào)錯(cuò)了,咋辦茎毁?參考這篇文章吧克懊,懶得寫了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末七蜘,一起剝皮案震驚了整個(gè)濱河市谭溉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌橡卤,老刑警劉巖扮念,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異碧库,居然都是意外死亡柜与,警方通過查閱死者的電腦和手機(jī)巧勤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旅挤,“玉大人踢关,你說我怎么就攤上這事≌城眩” “怎么了签舞?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長柒瓣。 經(jīng)常有香客問我儒搭,道長,這世上最難降的妖魔是什么芙贫? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任搂鲫,我火速辦了婚禮,結(jié)果婚禮上磺平,老公的妹妹穿的比我還像新娘魂仍。我一直安慰自己,他們只是感情好拣挪,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布擦酌。 她就那樣靜靜地躺著,像睡著了一般菠劝。 火紅的嫁衣襯著肌膚如雪赊舶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天赶诊,我揣著相機(jī)與錄音笼平,去河邊找鬼。 笑死舔痪,一個(gè)胖子當(dāng)著我的面吹牛寓调,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锄码,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼捶牢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了巍耗?” 一聲冷哼從身側(cè)響起秋麸,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎炬太,沒想到半個(gè)月后灸蟆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年炒考,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了可缚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡斋枢,死狀恐怖帘靡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瓤帚,我是刑警寧澤描姚,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站戈次,受9級特大地震影響轩勘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜怯邪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一绊寻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧悬秉,春花似錦澄步、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至允跑,卻和暖如春王凑,著一層夾襖步出監(jiān)牢的瞬間搪柑,已是汗流浹背聋丝。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留工碾,地道東北人弱睦。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像渊额,于是被迫代替她去往敵國和親况木。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345