Package
在Java中,Package是用來包含一系相關實例的集合。這些相關聯的實例包括:類、接口、異常、錯誤以及枚舉。
Package主要有一些的幾點作用:
-
Package可以處理名字沖突,在沖突的名字前加上包的名字,通過使用名字的全限定名來訪問名字的時候,可以避免名字沖突。因為在不同的包之間,具有不同的包名,所以可以通過全限定名來區分不同包中同名的名字。Package的這種機制稱為名字空間管理(Namespace Management)。
-
Package可以實現訪問控制,在Java中,除了常用的
public
和private
這兩個訪問控制修飾符外,還包含了protected
和default
這兩個訪問控制修飾符,這兩個修飾符都和Package相關。通過protected
修飾的實體,它的訪問受限於同一個包和它的子類中。如果一個實體沒有包含任何的訪問控制修飾符,那么默認就是default
,它的訪問受限於同一個包中。 -
用於發布可重用的類的集合,通常會將這些類打包成JAR包的形式。
Package的名字約定
一個包的名字可以通過將互聯網的域名反向后加上自己的項目名字產生。中間通過.
進行分隔。Package的名字采用小寫的方式。(i.e. 如果一個人的域名是'abc.com',那么他可以將自己項目的包名寫成 'com.abc.project')。
我們可能會看到在Java官方提供的包中,包含了java
和javax
前綴的包名,這兩個前綴分別用於官方提供的java包和java的擴展包。
包名和目錄結構的關系
Java中的包名和文件系統的目錄結構之間是有聯系的。同一個包中的實體,都被存儲在同一個目錄下,確切的說,這些實體被存儲在通過包名確定的子目錄結構下。i.e.,假設存在一個包名為 com.abc.project
的包,那么這個包中的實體被存儲在目錄$BASE_DIR/com/abc/project
下,其中的$BASE_DIR
表示了包的根目錄,也可以稱為Java中的類路徑。通過上面的例子,我們可以發現,將包名中的.
轉換為/
就是相應的子目錄結構了。
上面提到的$BASE_DIR
可以存在於文件系統的任何位置,所以Java的編譯器和虛擬機必須知道$BASE_DIR
的位置,才可以定位到需要的實體。對這個位置的查找是通過環境變量CLASSPATH
來實現的。CLASSPATH
是一個類似於PATH
的環境變量,只是CLASSPATH
是用於查找Java的類的位置的。
在Java中,沒有子包的概念。i.e.,兩個包java.awt
和java.awt.event
,這兩個包具有相同的前綴。其中包java.awt
中的實體存儲在路徑$BASE_DIR\java\awt
下,而包java.awt.event
中的實體存儲在路徑$BASE_DIR\java\awt\event
下,這是兩個獨立的包,只是具有相同的前綴而已,所以java.awt.event
不是java.awt
的子包。
例子
package com.yyy;
public class Circle {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
}
我們在./src/java/com/yyy
目錄下創建了一個com.yyy
的包,並且在包中創建了一個Circle類。然后我們需要將這個類的編譯后的class文件存儲在目錄classes
目錄下。
javac -d ./classes ./src/java/com/yyy/Circle.java
javac
命令會編譯這個java文件,然后將編譯后的class文件存儲在classes/com/yyy
目錄下,com/yyy
這個子目錄會根據包名com.yyy
自動生成。在這里,我們通過選項-d
來指定生成的類存放的位置的根目錄。
下面,我們使用上面創建的類對象:
import com.yyy.Circle;
public class TestCircle {
public static void main(String[] args) {
Circle c = new Circle(1.23);
System.out.println(c.getRadius());
}
}
我們在目錄./test
下創建類TestCircle
,但是我們不能直接編譯這個類:
cd test
javac TestCircle.java
上面的命令會報錯,提示找不到類com.yyy.Circle
,所以我們需要告訴編譯器該類的位置。通過選項-cp(-classpath)
可以指定類路徑的位置(這個路徑是包所在的根目錄,而不是包中類的目錄。通過這個根目錄,java會自動查找包中的類)。
javac -cp ./classes TestCircle.java
通過-cp
指定類路徑以后,可以順利編譯通過了,但是如果我們直接執行生成的class文件,還是會出現問題:
java TestCircle
上面的命令會報錯,提示找不到類com.yyy.Circle
,所以我們需要告訴命令哪里可以找到這個類,跟上面一樣,給出這個類所在的位置,也就是類路徑:
java -cp ./classses TestCircle
上面的命令,看似沒什么問題了,但是運行還是會報錯,但是這次提示找不到TestCircle類。這是因為java的CLASSPATH
如果沒有被顯式定義指定,那么默認值是當前目錄,所以第一次沒有報找不到TestCircle的錯誤。但是如果我們顯式更改CLASSPATH
的值(通過上面的-cp選項),那么就默認不會包含當前的目錄,如果需要包含當前的目錄,則需要顯式指定。在CLASSPATH
中可以通過:
分隔不同的路徑。
java -cp .:./classes TestCircle
如果TestCircle.java
被定義在包com.abc
中,那么如果我們在這個包的根目錄下引用這個類,則需要使用這個類的全限定名:
//TestCircle.java和Circle.java的class文件都被放在classes目錄下
javac -d classes -cp ./classes src/com/abc/TestCircle.java
//我們當前所在的目錄是包的根目錄,所以引用TestCircle的時候,需要使用全限定名
java -cp ./classes com.abc.TestCircle
CLASSPATH
CLASSPATH
是一個環境變量,Java虛擬機和編譯器會通過這個環境變量來查找java類和包的位置。
Java虛擬機查找類的方式
在討論Java的CLASSPATH環境變量之前,先介紹下Java虛擬機是如何查找Class文件的。
java虛擬機,也就是java
命令,通過以下的順序查找和加載java的class文件:
- Bootstrap classes - 構成java平台的class文件,包括rt.jar和一些其他的重要的jar文件
- Extension classes - java的擴展機制的class文件,這些class文件以jar的方式組織並存在於擴展目錄中
- User classes - 開發者和第三方創建的class文件,這些class文件的位置通過
-classpath(-cp)
選項來指定,或者通過設置環境變量CLASSPATH
來指定。
Java虛擬機查找Bootstrap classes的方式
Bootstrap classes是一些用於實現Java 2平台的class文件。Bootstrap classes包含在rt.jar和一些其他的jar文件中,這些jar文件放在jre/lib
目錄下。這些包是通過bootstrap class路徑指定的,這個路徑值存儲在sun.boot.class.path
這個系統屬性中,這個系統屬性應該是只讀的,不應該直接修改。
一般情況下,bootstrap classes路徑是不應該被重新修改的。Java的非標准選項-Xbootclasspath
,允許修改這個路徑值來進行自定義定制核心class。
需要指出的是,實現Java 2 SDK的工具class文件並不和bootstrap class文件存放在一起。這些工具sdk存放在目錄/lib/tools.jar
下。開發工具會在啟動Java虛擬機的時候將這個jar包添加到user class路徑中。然而,這個增強的user class路徑只是用於執行這些工具,對於處理源代碼的工具,如javac
和javadoc
,它們使用的是原始的class路徑,而不是這個增強版本的class路徑。
Java虛擬機查找Extension classes的方式
Extension classes是一些用於擴展Java平台的class文件。這些用於擴展Java平台的class文件被組織成jar包的形式,存儲在jre/lib/ext
目錄下。在這個目錄下的jar文件會通過Java Extension Framework進行加載。在這個目錄下的松散的class文件不會被查找,這些class文件必須是以jar/zip包的形式打包以后才可以被查找。這個擴展包存放的目錄的位置是不能被修改的。
在目錄jre/lib/ext
目錄下如果存在多個jar文件,並且這些jar包中包含了同名的class文件,如:
smart-extension1_0.jar contains class smart.extension.Smart
smart-extension1_1.jar contains class smart.extension.Smart
那么,具體加載上面哪個smart.extension.Smart類是未定義的。
Java虛擬機查找User classes的方式
Uesr Classes是一些在java平台上創建的class文。Java虛擬機通過引用 user class path 來查找這些class文件的位置, user class path 是一個包含了class文件的目錄、jar包和zip包的路徑列表。
一個class文件擁有一個反映類的全限定名的子路徑名,如:假設有一個名為com.mypackage.MyClass
的類,並且存放在目錄/myClasses
下,那么目錄myClasses
必須在 user class path 中,並且MyClass這個類的路徑必須是/myClasses/com/mypackage/MyClass.class
。如果這個類被存儲在myclasses.jar
這個jar包中,那么myclasses.jar
這個包必須在 user class path 中,並且這個類在jar包中的路徑必須是com/mypackage/MyClass.class
。
用戶類路徑,也就是 user class path ,是以字符串的形式指定的,在Unix下通過:
進行分隔,而Win下使用;
進行分隔。Java虛擬機會將用戶類路徑中的字符串添加到java.class.path
系統屬性中,這個屬性的值有幾種設置途徑:
- 默認值是
.
,表示當前目錄 - 環境變量
CLASSPATH
的值,這個值會覆蓋默認值。 - 在命令行中通過
-cp / -classpath
選項指定的路徑值,這個值會覆蓋默認值和CLASSPATH
環境變量的值。 - 在命令行中通過
-jar
選項指定的jar包的路徑值,這個值會覆蓋上面所有的值,如果這個選項被設置,那么所有的用戶類必須通過jar包的形式指定。
通過上面列出的規則,我們可以知道,CLASSPATH
環境變量的值只是Java查找類的一中方式。
Java虛擬機查找jar包中的類的方式
一個jar包文件通常包含一個稱為manifest
的文件,這個文件包含了這個jar包中的內容。這個manifest文件可以定義一個稱為JAR-class-path
的類路徑,這個類路徑可以用於擴展Java的類路徑(前提是這個jar包被加載進來)。通過JAR-class-path
訪問類的順序定義如下:
- 一般情況下,在一個jar包中的所有class文件都是通過一個JAR-class-path入口引用的,在查找的時候也是以JAR-class-path入口在類路徑中的先后順序進行訪問的。
- 如果JAR-class-path指向的jar包文件已經被查找過,那么這個jar包文件將不會被再次查找(這種優化可以提高效率,並且避免循環查找)。
- 如果一個jar包文件是作為一個擴展包安裝的,那么這個jar包定義的JAR-class-path會被忽略。所有的在這個jar包中的類文件會被認為是SDK的一部分,或者已經被作為擴展包安裝。
引用
http://docs.oracle.com/javase/7/docs/technotes/tools/findingclasses.html
http://www.ntu.edu.sg/home/ehchua/programming/java/J9c_PackageClasspath.html