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

前言

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

例子1

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

首先創(chuàng)建一個(gè)文件夾TestClassPath我們所有的例子都會(huì)放到這個(gè)文件夾中.然后在文件夾下創(chuàng)建一個(gè)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

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

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

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

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: 這個(gè)就是我們自己編譯生成的class文件或者引入的第三方的class文件. 用-classpath(縮寫-cp)來(lái)表示他們的路徑.

再來(lái)一段:
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).

請(qǐng)看黑體部分,通常情況下,我們只需要定義好自己的classpath就可以了,因?yàn)?code>Bootstrap classes和extension classes編譯器會(huì)自動(dòng)去找的.

看到這里有沒(méi)有對(duì)上面的問(wèn)題豁然開朗,如果還沒(méi)有理解沒(méi)關(guān)系,我們先打印一下對(duì)應(yīng)的Bootstrap classes路徑和當(dāng)前user classes路徑看看編譯器都可以找到哪些class文件.(由于Extension classes路徑已經(jīng)知道了就不多說(shuō)了),補(bǔ)充說(shuō)明一下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:.

所以我們上面的問(wèn)題到這里就算是解決了,因?yàn)?code>BootStrap Classes路徑中包含有rt.jar,因此也就是說(shuō)編譯器會(huì)自動(dòng)去找到需要的java.util.Arrays,java.util.ArrayListjava.lang.System等等.

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

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

CLASSPATH規(guī)則

另外我們注意到從上面例子的輸出中看到:
當(dāng)我們沒(méi)有給系統(tǒng)設(shè)置CLASSPATH``的時(shí)候,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名稱,沒(méi)有的話就表示沒(méi)有package,那這個(gè)類的名稱就是包名.類名,看下面的一個(gè)例子.

例子2

接著上面的路徑哈,在TestClassPath目錄下創(chuàng)建一個(gè)目錄example1,并且在example1目錄下創(chuàng)建一個(gè)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

后會(huì)輸出

in Test2
current user clases path : .

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

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

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

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

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

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

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

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

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

還有一點(diǎn)要注意的是: java命令只會(huì)搜索你給的classpath去搜索,不會(huì)去子目錄下搜索的,比如java -cp ../../ example1/Test2也是不行的, 那有人就會(huì)問(wèn)java -cp ../../../ TestClassPath/example1/Test2行不行?當(dāng)然不行,因?yàn)轭惷?code>example1/Test2不是TestClassPath/example1/Test2

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

例子3

如果你對(duì)上面的還有些許疑問(wèn)的話,我們?cè)倏磦€(gè)例子.
example1目錄下創(chuàng)建個(gè)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)如下:(注意,我們還沒(méi)有創(chuàng)建example2)

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

通過(guò)上面的學(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é)下:編譯的時(shí)候我們要關(guān)心

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

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

執(zhí)行的時(shí)候因?yàn)?code>Test3.class對(duì)應(yīng)的類名是example2.Test3,因此如果需要執(zhí)行Test3.class的話,必須有相對(duì)應(yīng)的文件目錄結(jié)構(gòu).因此我們創(chuàng)建一個(gè)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

此時(shí)執(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

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

example1創(chuàng)建一個(gè)文件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"));
    }
}

此時(shí)目錄結(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
其次是該源文件,相對(duì)路徑和絕對(duì)路徑都可以

所以
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文件

此時(shí)需要關(guān)注包的存在了,不過(guò)Test4.java文件名中沒(méi)有包(有興趣的人可以自己加個(gè)包實(shí)驗(yàn)一下,道理都是一樣的)
因?yàn)閳?zhí)行Test4.class里面用到example2/Test3.class,所以-cp命令需要找到這兩個(gè)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é)果就不貼了,運(yùn)行成功了就可以.

結(jié)尾

如果哪里寫得有問(wèn)題,歡迎留言大家一起討論哈.
如果對(duì)你有幫助, 請(qǐng)點(diǎn)個(gè)贊吧, 哈哈, 多謝!

參考

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

?著作權(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)容

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

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