命名空間
包名
????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ò)了,咋辦茎毁?參考這篇文章吧克懊,懶得寫了。