通過例子理解java中的classpath(-cp)

前言

javac是用來把后綴名為java的文件編譯成class文件(字節(jié)碼),然后java命令把對應(yīng)的class文件執(zhí)行就可以看到你程序里面定義的操作了.
首先我們知道classpath顧名思義就是class文件的路徑,那-cp就是我們可以指定class文件的路徑.
整篇文章的目的和主題只有一個就是弄明白classpath.
先看一個例子簡單了解一下.
所有代碼: 源代碼

例子1

我的電腦目前執(zhí)行echo $CLASSPATH是空,也就是說沒有設(shè)置系統(tǒng)CLASSPATH.

首先創(chuàng)建一個文件夾TestClassPath我們所有的例子都會放到這個文件夾中.然后在文件夾下創(chuàng)建一個Test1.java內(nèi)容如下:

import java.util.ArrayList;
import java.util.Arrays;
public class Test1 {
    public static void main(String[] args) {
        System.out.println("in Test1");
        ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5));
        for (int i : list) System.out.println("element:" + i);
    }
}

至此目錄結(jié)構(gòu)如下:

.
└── Test1.java

執(zhí)行javac Test1.java后目錄如下:

.
├── Test1.class
└── Test1.java

執(zhí)行java Test1后輸出

in Test1
element:1
element:2
element:3
element:4
element:5

至此一個簡單的例子就結(jié)束了,通過這一個例子我們應(yīng)該知道了編譯和執(zhí)行的過程,但是卻引出了一個問題

1.為什么CLASSPATH是空的時候javac也可以編譯成功?難道不需要引入java.util.ArrayList類和java.util.Arrays類編譯后的class文件嗎?

答案:
看一下官網(wǎng)的解釋吧How Classes are Found
摘錄下來幾句話

The Java launcher, java, initiates the Java virtual machine. The virtual machine searches for and loads classes in this order:

Bootstrap classes - Classes that comprise the Java platform, including the classes in rt.jar and several other important jar files.
Extension classes - Classes that use the Java Extension mechanism. These are bundled as .jar files located in the extensions directory.
User classes - Classes defined by developers and third parties that do not take advantage of the extension mechanism. You identify the location of these classes using the -classpath option on the command line (the preferred method) or by using the CLASSPATH environment variable.

大概意思就是分三種類型的class文件:
Bootstrap classes: 是一些在rt.jar和一些其他jar包的class文件.
Extension classes:在JAVA_HOME/jre/lib/extjar包的class文件.
User Classes: 這個就是我們自己編譯生成的class文件或者引入的第三方的class文件. 用-classpath(縮寫-cp)來表示他們的路徑.

再來一段:
It is relatively difficult to accidentally "hide" or omit the bootstrap classes.
In general, you only have to specify the location of user classes. Bootstrap classes and extension classes are found "automatically".
The tools classes are now in a separate archive (tools.jar) and can only be used if included in the user class path (to be explained shortly).

請看黑體部分,通常情況下,我們只需要定義好自己的classpath就可以了,因為Bootstrap classesextension classes編譯器會自動去找的.

看到這里有沒有對上面的問題豁然開朗,如果還沒有理解沒關(guān)系,我們先打印一下對應(yīng)的Bootstrap classes路徑和當(dāng)前user classes路徑看看編譯器都可以找到哪些class文件.(由于Extension classes路徑已經(jīng)知道了就不多說了),補充說明一下java.util.*,java.lang.*都是在rt.jar中.

在上面的Test1.java中加入兩句話

import java.util.ArrayList;
import java.util.Arrays;
public class Test1 {
    public static void main(String[] args) {
        System.out.println("in Test1");
        ArrayList<Integer> list = new ArrayList<>(Arrays.asList(1,2,3,4,5));
        for (int i : list) System.out.println("element:" + i);
        System.out.println("Bootclasspath:" + System.getProperty("sun.boot.class.path"));
        System.out.println("user classes:" + System.getProperty("java.class.path"));
    }
}

我的電腦上的輸出如下:

in Test1
element:1
element:2
element:3
element:4
element:5
Bootclasspath:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_171.jdk/Contents/Home/jre/classes
user classes:.

所以我們上面的問題到這里就算是解決了,因為BootStrap Classes路徑中包含有rt.jar,因此也就是說編譯器會自動去找到需要的java.util.Arrays,java.util.ArrayListjava.lang.System等等.

那讓我們做個小測試吧(-bootclasspath命令可以修改BootStrap Classes的路徑),如果執(zhí)行javac -bootclasspath ./ Test1.java看看能不能編譯成功,也就是把BootStrap Classes路徑改為當(dāng)前路徑,那此時編譯器肯定找不到相應(yīng)的java.langjava.util下面的class文件了,運行結(jié)果與我們分析一致.

Fatal Error: Unable to find package java.lang in classpath or bootclasspath

CLASSPATH規(guī)則

另外我們注意到從上面例子的輸出中看到:
當(dāng)我們沒有給系統(tǒng)設(shè)置CLASSPATH``的時候,User Classes```路徑為當(dāng)前路徑. 規(guī)則如下:

1. The default value, ".", meaning that user class files are all the class files in the current directory (or under it, if in a package).
2. The value of the CLASSPATH environment variable, which overrides the default value.
3. The value of the -cp or -classpath command line option, which overrides both the default value and the CLASSPATH value.
4. The JAR archive specified by the -jar option, which overrides all other values. If this option is used, all user classes must come from the specified archive.

總結(jié):

默認(rèn)路徑(當(dāng)前路徑) < 系統(tǒng)設(shè)置CLASSPATH < 命令行設(shè)置的CLASSPATH < -jar 命令

package

Java classes are organized into packages which are mapped to directories in the file system.
Java文件中的第一行就是package名稱,沒有的話就表示沒有package,那這個類的名稱就是包名.類名,看下面的一個例子.

例子2

接著上面的路徑哈,在TestClassPath目錄下創(chuàng)建一個目錄example1,并且在example1目錄下創(chuàng)建一個Test2.java內(nèi)容如下:

package example1;

public class Test2 {
    public static void main(String[] args) {
        System.out.println("in Test2");
        System.out.println("current user clases path : " + System.getProperty("java.class.path"));
    }
}

當(dāng)前目錄結(jié)構(gòu)如下:

.
├── Test1.class
├── Test1.java
└── example1
    └── Test2.java

當(dāng)前路徑也就是TestClassPath目錄下執(zhí)行:

javac example1/Test2.java
java exampe/Test2

后會輸出

in Test2
current user clases path : .

相信這個對于如果理解了前面所講的應(yīng)該不難理解.
那我們就給自己找點事情做進(jìn)入到example1中的目錄中進(jìn)行編譯Test2.java

cd example1
那應(yīng)該如何寫編譯語句呢?有以下幾種方案

1. 有的人可能會認(rèn)為類名不是example/Test2.java嗎?應(yīng)該寫javac example1/Test2.java
2. 有的人可能會認(rèn)為1.中的語句根本找不到源文件啊,是不是應(yīng)該寫javac -cp ../ example1/Test2.java
3. 還有一種就是直接執(zhí)行javac Test2.java

對于上面幾種寫法
只有3是對的,為什么呢?我們明白的是-cp指定的是源文件需要用到的自己編譯好的class文件或者第三方class文件, 又因為源文件中Test2.java并不需要什么其他的class文件,所以可以直接編譯.
那為什么1錯了呢?因為javac找不到需要編譯的源文件Test2.java, 換成javac ../example1/Test2.java就沒有問題了.
至于2錯誤的原因和1一樣找不到源文件,雖然定義了-cp但是在這里并沒有什么太大的意義,因為源文件Test2.java不需要引入什么class文件.

javac Test2.java編譯后目錄結(jié)構(gòu)如下:

├── Test1.class
├── Test1.java
└── example1
    ├── Test2.class
    └── Test2.java
那如何執(zhí)行這個Test2.class文件呢?

前提,當(dāng)前位置是在example1中.

1. java Test2
2. java example1/Test2
3. java -cp ../ example1/Test2

3 是對的, 因為我們需要執(zhí)行的是一個class文件, 而且必須要確保這個class對應(yīng)的類名是有相對應(yīng)的文件夾存在的,比如com.test.Test.class在文件目錄中表示是必須有com/test/Test.class(就是com文件夾下有個test文件夾,test文件夾有個Test.class) 等下會用例子3來具體說明這個問題, 因此我們需要指定這個要執(zhí)行的class文件所在的路徑可以讓java命令找到該class文件. 因此我們指定了-cp ../告訴java命令在上層目錄來尋找要執(zhí)行的example1/Test2.class.

還有一點要注意的是: java命令只會搜索你給的classpath去搜索,不會去子目錄下搜索的,比如java -cp ../../ example1/Test2也是不行的, 那有人就會問java -cp ../../../ TestClassPath/example1/Test2行不行?當(dāng)然不行,因為類名是example1/Test2不是TestClassPath/example1/Test2

如果明白了這些,對12就可以好判斷了,1中類名錯誤,2中根據(jù)CLASSPATH規(guī)則CLASSPATH是當(dāng)前目錄,但是當(dāng)前目錄找不到對應(yīng)的class文件.

例子3

如果你對上面的還有些許疑問的話,我們再看個例子.
example1目錄下創(chuàng)建個Test3.java內(nèi)容如下:

package example2;
public class Test3 {
    public static void main(String[] args) {
        System.out.println("in Test3");
        System.out.println("current user clases path : " + System.getProperty("java.class.path"));
    }
}

目錄結(jié)構(gòu)如下:(注意,我們還沒有創(chuàng)建example2)

.
├── Test1.class
├── Test1.java
└── example1
    ├── Test2.class
    ├── Test2.java
    └── Test3.java
如何編譯該Test3.java文件

通過上面的學(xué)習(xí)我們應(yīng)該可以知道以下兩種方式都可以編譯Test3.java文件
如果當(dāng)前位置在TestClassPath目錄下可以使用:javac example1/Test3.java
如果當(dāng)前位置在example1目錄下可以使用:javac Test3.java

編譯后的文件結(jié)構(gòu)

.
├── Test1.class
├── Test1.java
└── example1
    ├── Test2.class
    ├── Test2.java
    ├── Test3.class
    └── Test3.java

稍微總結(jié)下:編譯的時候我們要關(guān)心

1. 該源文件需要依賴哪些本地class文件和第三方class文件, 可以用-cp來指定.
2. 源文件自己的路徑?jīng)]有錯誤,意思是只需要給出源文件的相對目錄或者絕對目錄都可以,此時跟該源文件是否有包都沒有關(guān)系(可以觀察上面文件結(jié)構(gòu)還沒有創(chuàng)建example2文件夾).

如何執(zhí)行該Test3.class文件

執(zhí)行的時候因為Test3.class對應(yīng)的類名是example2.Test3,因此如果需要執(zhí)行Test3.class的話,必須有相對應(yīng)的文件目錄結(jié)構(gòu).因此我們創(chuàng)建一個example2文件夾并且把Test3.class移到example2目錄下.

當(dāng)前位置在TestClassPath

mkdir example2
mv example1/Test3.class example2

目前目錄結(jié)構(gòu)如下:

.
├── Test1.class
├── Test1.java
├── example1
│   ├── Test2.class
│   ├── Test2.java
│   └── Test3.java
└── example2
    └── Test3.class

此時執(zhí)行可以有以下幾種形式
1. 當(dāng)前位置在TestClassPath下, java example2/Test3
2. 當(dāng)前位置在example1下, java -cp ../ example2/Test3
3.當(dāng)前位置在example2下,java -cp ../ example2/Test3

例子4

上面的例子都是沒有引用本地class文件的,這個例子就看看如何引用本地class文件,第三方class文件就是一樣的道理了.

example1創(chuàng)建一個文件Test4.java內(nèi)容如下:

import example2.Test3;

public class Test4 {
    public static void main(String[] args) {
        Test3 test3 = new Test3();
        System.out.println("test3:" + test3);
        System.out.println("java.class.path:" + System.getProperty("java.class.path"));
    }
}

此時目錄結(jié)構(gòu)如下:

.
├── Test1.class
├── Test1.java
├── example1
│   ├── Test2.class
│   ├── Test2.java
│   ├── Test3.java
│   └── Test4.java
└── example2
    └── Test3.class
如何編譯該Test4.java

首先分析該源文件需要用到的user classes,在這里是用example2.Test3因此-cp要指向該example2.Test3
其次是該源文件,相對路徑和絕對路徑都可以

所以
1. 如果在TestClassPath目錄下: javac -cp ./ example1/Test4.javajavac example1/Test4.java
2. 如果在example1目錄下:javac -cp ../ Test4.java
3. 如果在example2目錄下:javac -cp ../ ../example1/Test4.java

不管采用哪種編譯方式,編譯后目錄結(jié)構(gòu)如下:

.
├── Test1.class
├── Test1.java
├── example1
│   ├── Test2.class
│   ├── Test2.java
│   ├── Test3.java
│   ├── Test4.class
│   └── Test4.java
└── example2
    └── Test3.class
如何執(zhí)行該Test4.class文件

此時需要關(guān)注包的存在了,不過Test4.java文件名中沒有包(有興趣的人可以自己加個包實驗一下,道理都是一樣的)
因為執(zhí)行Test4.class里面用到example2/Test3.class,所以-cp命令需要找到這兩個class文件

1. 如果在TestClassPath中, java -cp ./:example2/ Test4 (其中./是為找到example1/Test3.class,example2/是為了找到Test4.class)
2. 如果在example1中,java -cp ../:./ Test4 (其中../是為找到example1/Test3.class,./是為了找到Test4.class)
3.如果在example2中,java -cp ../:../example1/ Test4 (其中../是為找到example1/Test3.class,../example1/ Test4是為了找到Test4.class)

輸出結(jié)果就不貼了,運行成功了就可以.

結(jié)尾

如果哪里寫得有問題,歡迎留言大家一起討論哈.
如果對你有幫助, 請點個贊吧, 哈哈, 多謝!

參考

1. How Classes are Found
2. Setting the class path

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內(nèi)容

  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom閱讀 2,716評論 0 3
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,816評論 18 139
  • 一、Python簡介和環(huán)境搭建以及pip的安裝 4課時實驗課主要內(nèi)容 【Python簡介】: Python 是一個...
    _小老虎_閱讀 5,794評論 0 10
  • “真對不起,是我誤會了。”蘇夏的麻麻撓了撓后腦勺,“真是的,孩子他爸你都多少歲了,還拿這種事情開玩笑。” “這和多...
    口十木木閱讀 392評論 0 0
  • 火龍果成長記錄 搗碎火龍果種子,泡水里一晚上,靜止,將果肉揉碎,種子會沉盆地,分離出種子 種子粘在了衛(wèi)生紙生(? ̄...
    丫丫耳閱讀 479評論 0 1