前言
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 bundledas .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/ext
中jar
包的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 classes
和extension 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.ArrayList
和java.lang.System
等等.
那讓我們做個小測試吧(
-bootclasspath
命令可以修改BootStrap Classes
的路徑),如果執(zhí)行javac -bootclasspath ./ Test1.java
看看能不能編譯成功,也就是把BootStrap Classes
路徑改為當(dāng)前路徑,那此時編譯器肯定找不到相應(yīng)的java.lang
和java.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
如果明白了這些,對1和2就可以好判斷了,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.java
或javac 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é)尾
如果哪里寫得有問題,歡迎留言大家一起討論哈.
如果對你有幫助, 請點個贊吧, 哈哈, 多謝!