前言
javac
是用来把后缀名为java
的文件编译成class
文件(字节码),然后java
命令把对应的class
文件执行就可以看到你程序里面定义的操作了.
首先我们知道classpath
顾名思义就是class
文件的路径,那-cp
就是我们可以指定class
文件的路径.
整篇文章的目的和主题只有一个就是弄明白classpath
.
先看一个例子简单了解一下.
所有代码: 源代码
例子1
我的电脑目前执行
echo $CLASSPATH
是空,也就是说没有设置系统CLASSPATH
.
首先创建一个文件夹
TestClassPath
我们所有的例子都会放到这个文件夹中.然后在文件夹下创建一个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 list = new ArrayList<>(Arrays.asList(1,2,3,4,5));
for (int i : list) System.out.println("element:" + i);
}
}
至此目录结构如下:
.
└── Test1.java
执行
javac Test1.java
后目录如下:
.
├── Test1.class
└── Test1.java
执行
java Test1
后输出
in Test1
element:1
element:2
element:3
element:4
element:5
至此一个简单的例子就结束了,通过这一个例子我们应该知道了编译和执行的过程,但是却引出了一个问题
1.为什么
CLASSPATH
是空的时候javac
也可以编译成功?难道不需要引入java.util.ArrayList
类和java.util.Arrays
类编译后的class
文件吗?
答案:
看一下官网的解释吧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
编译器会自动去找的.
看到这里有没有对上面的问题豁然开朗,如果还没有理解没关系,我们先打印一下对应的
Bootstrap classes
路径和当前user classes
路径看看编译器都可以找到哪些class
文件.(由于Extension classes
路径已经知道了就不多说了),补充说明一下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 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
的路径),如果执行javac -bootclasspath ./ Test1.java
看看能不能编译成功,也就是把BootStrap Classes
路径改为当前路径,那此时编译器肯定找不到相应的java.lang
和java.util
下面的class
文件了,运行结果与我们分析一致.
Fatal Error: Unable to find package java.lang in classpath or bootclasspath
CLASSPATH规则
另外我们注意到从上面例子的输出中看到:
当我们没有给系统设置CLASSPATH``的时候,
User Classes```路径为当前路径. 规则如下:
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.
总结:
默认路径(当前路径) < 系统设置
CLASSPATH
< 命令行设置的CLASSPATH
<-jar
命令
包package
Java classes are organized into packages which are mapped to directories in the file system.
在Java
文件中的第一行就是package
名称,没有的话就表示没有package
,那这个类的名称就是包名.类名,看下面的一个例子.
例子2
接着上面的路径哈,在
TestClassPath
目录下创建一个目录example1
,并且在example1
目录下创建一个Test2.java
内容如下:
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"));
}
}
当前目录结构如下:
.
├── Test1.class
├── Test1.java
└── example1
└── Test2.java
当前路径也就是
TestClassPath
目录下执行:
javac example1/Test2.java
java exampe/Test2
后会输出
in Test2
current user clases path : .
相信这个对于如果理解了前面所讲的应该不难理解.
那我们就给自己找点事情做进入到example1
中的目录中进行编译Test2.java
cd example1
那应该如何写编译语句呢?有以下几种方案
1. 有的人可能会认为类名不是
example/Test2.java
吗?应该写javac example1/Test2.java
2. 有的人可能会认为1.中的语句根本找不到源文件啊,是不是应该写javac -cp ../ example1/Test2.java
3. 还有一种就是直接执行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
编译后目录结构如下:
├── Test1.class
├── Test1.java
└── example1
├── Test2.class
└── Test2.java
那如何执行这个Test2.class
文件呢?
前提,当前位置是在
example1
中.
1.
java Test2
2.java example1/Test2
3.java -cp ../ example1/Test2
3 是对的, 因为我们需要执行的是一个
class
文件, 而且必须要确保这个class
对应的类名是有相对应的文件夹存在的,比如com.test.Test.class
在文件目录中表示是必须有com/test/Test.class
(就是com
文件夹下有个test
文件夹,test
文件夹有个Test.class
) 等下会用例子3来具体说明这个问题, 因此我们需要指定这个要执行的class
文件所在的路径可以让java
命令找到该class
文件. 因此我们指定了-cp ../
告诉java
命令在上层目录来寻找要执行的example1/Test2.class
.
还有一点要注意的是:
java
命令只会搜索你给的classpath
去搜索,不会去子目录下搜索的,比如java -cp ../../ example1/Test2
也是不行的, 那有人就会问java -cp ../../../ TestClassPath/example1/Test2
行不行?当然不行,因为类名是example1/Test2
不是TestClassPath/example1/Test2
如果明白了这些,对1和2就可以好判断了,1中类名错误,2中根据
CLASSPATH
规则CLASSPATH
是当前目录,但是当前目录找不到对应的class
文件.
例子3
如果你对上面的还有些许疑问的话,我们再看个例子.
在example1
目录下创建个Test3.java
内容如下:
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"));
}
}
目录结构如下:(注意,我们还没有创建
example2
)
.
├── Test1.class
├── Test1.java
└── example1
├── Test2.class
├── Test2.java
└── Test3.java
如何编译该Test3.java
文件
通过上面的学习我们应该可以知道以下两种方式都可以编译
Test3.java
文件
如果当前位置在TestClassPath
目录下可以使用:javac example1/Test3.java
如果当前位置在example1
目录下可以使用:javac Test3.java
编译后的文件结构
.
├── Test1.class
├── Test1.java
└── example1
├── Test2.class
├── Test2.java
├── Test3.class
└── Test3.java
稍微总结下:编译的时候我们要关心
1. 该源文件需要依赖哪些本地
class
文件和第三方class
文件, 可以用-cp
来指定.
2. 源文件自己的路径没有错误,意思是只需要给出源文件的相对目录或者绝对目录都可以,此时跟该源文件是否有包都没有关系(可以观察上面文件结构还没有创建example2
文件夹).
如何执行该Test3.class
文件
执行的时候因为
Test3.class
对应的类名是example2.Test3
,因此如果需要执行Test3.class
的话,必须有相对应的文件目录结构.因此我们创建一个example2
文件夹并且把Test3.class
移到example2
目录下.
当前位置在
TestClassPath
mkdir example2
mv example1/Test3.class example2
目前目录结构如下:
.
├── Test1.class
├── Test1.java
├── example1
│ ├── Test2.class
│ ├── Test2.java
│ └── Test3.java
└── example2
└── Test3.class
此时执行可以有以下几种形式
1. 当前位置在TestClassPath
下,java example2/Test3
2. 当前位置在example1
下,java -cp ../ example2/Test3
3.当前位置在example2
下,java -cp ../ example2/Test3
例子4
上面的例子都是没有引用本地
class
文件的,这个例子就看看如何引用本地class
文件,第三方class
文件就是一样的道理了.
在
example1
创建一个文件Test4.java
内容如下:
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"));
}
}
此时目录结构如下:
.
├── 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
不管采用哪种编译方式,编译后目录结构如下:
.
├── Test1.class
├── Test1.java
├── example1
│ ├── Test2.class
│ ├── Test2.java
│ ├── Test3.java
│ ├── Test4.class
│ └── Test4.java
└── example2
└── Test3.class
如何执行该Test4.class
文件
此时需要关注包的存在了,不过
Test4.java
文件名中没有包(有兴趣的人可以自己加个包实验一下,道理都是一样的)
因为执行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
)
输出结果就不贴了,运行成功了就可以.
结尾
如果哪里写得有问题,欢迎留言大家一起讨论哈.
如果对你有帮助, 请点个赞吧, 哈哈, 多谢!
参考
1. How Classes are Found
2. Setting the class path