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

前言

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<Integer> 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 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编译器会自动去找的.

看到这里有没有对上面的问题豁然开朗,如果还没有理解没关系,我们先打印一下对应的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<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的路径),如果执行javac -bootclasspath ./ Test1.java看看能不能编译成功,也就是把BootStrap Classes路径改为当前路径,那此时编译器肯定找不到相应的java.langjava.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

如果明白了这些,对12就可以好判断了,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.javajavac 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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容

  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom阅读 2,694评论 0 3
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,638评论 18 139
  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 5,734评论 0 10
  • “真对不起,是我误会了。”苏夏的麻麻挠了挠后脑勺,“真是的,孩子他爸你都多少岁了,还拿这种事情开玩笑。” “这和多...
    口十木木阅读 380评论 0 0
  • 火龙果成长记录 捣碎火龙果种子,泡水里一晚上,静止,将果肉揉碎,种子会沉盆地,分离出种子 种子粘在了卫生纸生(๑‾...
    丫丫耳阅读 463评论 0 1