Featured image of post Java ClassPath

Java ClassPath

Java中一个重要配置,ClassPath的学习

ClassPath是Java中一个重要的环境变量,弄懂他对于Java运行环境的理解有重大的帮助

ClassPath是什么

CLASSPATH直译过来是类路径,是Java环境配置中要设置的一个环境变量,就是.class文件的路径,表示JVM从哪里去寻找要运行的class文件CLASSPATH = D:\java表示执行java命令时去D:\java目录中去找需要被执行的class文件并运行。

体验package

先来体验一个正常的Java项目,目录结构如下

1
2
3
4
5
6
7
./
└── com
    └── study
        ├── java
        │   ├── MainTest.java
        └── school
            └── Student.java

Student.java内容如下

1
2
3
4
5
6
7
package com.study.school;

public class Student {
    public void study() {
        System.out.println("I'm studying...");
    }
}

MainTest.java内容如下

1
2
3
4
5
6
7
8
9
package com.study.java;
import com.study.school.Student;

public class MainTest {
    public static void main(String[] args) {
        Student s = new Student();
        s.study();
    }
}

运行MainTest.java中的main(),可以得到

image-20241107152808239

正常调用

可以看到,两个Java类处于不同的目录,也能正常调用,前提是MainTest.java识别到了Student.java的包路径,准确importcom.study.school.Student,这个包路径是package定义的!

体验ClassPath

首先我们整个目录结构如下

1
2
./
├── MainTest.java

我们先关注MainTest.java,看看他的内容

1
2
3
4
5
public class MainTest {
    public static void main(String[] args){
        System.out.println("hello-hi");
    }
}

很简单,只是一句输出,那么我们先执行javac命令,将他编译为字节码文件,再使用java命令去执行他

1
2
3
4
5
6
7
8
9
C:\Users\Dan\Desktop\pppppp>"D:\Program Files\Java\jdk1.8.0_181\bin\javac.exe" MainTest.java
# 此时产生了 MainTest.class 在 MainTest.java 同级路径下,如下,
# ./
# ├── MainTest.class # 默认和.java文件同路径
# ├── MainTest.java  # 源.java文件

C:\Users\Dan\Desktop\pppppp>"D:\Program Files\Java\jdk1.8.0_181\bin\java.exe" MainTest
hello-hi
# 符合预期

目前为止所有的输出都符合预期。

但同时我们也理解,一个大型的,或者可拓展性强的Java项目,是按照模块化思维来进行开发的,所以不同的功能会交给不同的包来执行,Java提供了package关键字来做到这点。正如上面的案例一样

为了模拟项目环境,我们需要修改目录结构以及.java的内容

1
2
3
4
5
./
├── MainTest.java
├── bin
└── src
    └── MainTestClassPath.java

那我们稍微修改一下MainTest.java,让他处于包环境内,变成MainTestClassPath.java,只添加了package关键字以及包路径

MainTestClassPath.java内容如下

1
2
3
4
5
6
7
package com.study.java;

public class MainTestClassPath {
    public static void main(String[] args){
        System.out.println("hello-hi");
    }
}

此时我们再执行javac命令,将他编译为字节码文件,再使用java命令去执行他

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
C:\Users\Dan\Desktop\pppppp>javac.exe .\src\MainTestClassPath.java
# 此时产生了 MainTestClassPath.class 在 MainTestClassPatht.java 同级路径下,如下是当前的目录结构,
# ./
# ├── MainTest.java
# ├── bin
# └── src
#     ├── MainTestClassPath.class
#     └── MainTestClassPath.java

C:\Users\Dan\Desktop\pppppp>java.exe .\src\MainTestClassPath
错误: 找不到或无法加载主类 C:\Users\Dan\Desktop\pppppp\src\MainTestClassPath

报错了,为什么?原因很清楚的告知了我们,没有找到或者无法加载主类,但是我们运行的类很简单,不应该出现类层面的问题,唯一出现的问题,就是添加了package关键字。

没有找到类,是因为ClassLoader查找失败了

这些问题是怎么形成的,暂时按下不表,至于如何修复,或者说如何正确调用呢?

解决

步骤应该如下

目录结构没有变化

1
2
3
4
./
├── bin
└── src
    └── MainTestClassPath.java

MainTestClassPath.java内容也不变

1
2
3
4
5
6
7
package com.study.java;

public class MainTestClassPath {
    public static void main(String[] args){
        System.out.println("hello-hi");
    }
}

执行如下命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
C:\Users\Dan\Desktop\pppppp>javac.exe .\src\MainTestClassPath.java -d bin
# 执行完此命令后,目录结构如下
# ./
# ├── MainTest.java
# ├── bin
# │   └── com
# │       └── study
# │           └── java
# │               └── MainTestClassPath.class
# └── src
#     └── MainTestClassPath.java

C:\Users\Dan\Desktop\pppppp>java.exe -classpath bin com.study.java.MainTestClassPath
# 成功
hello-hi

# 也可以
C:\Users\Dan\Desktop\pppppp>cd bin
C:\Users\Dan\Desktop\pppppp\bin>java.exe com.study.java.MainTestClassPath
#成功
hello-hi

成功执行了,为什么,不同的点在于使用了 -d 参数,-d指定了生成目录,在bin目录下生成。

这个时候javac自动创建了多级目录,按照package.号作为目录划分符,将创建好的MainTestClassPath.class放在了目录最深处

然后使用java命令,指定classpathbin目录,执行com.study.java.MainTestClassPath类时,ClassLoader会在bin目录下寻找com.study.java.MainTestClassPath类,并且将.作为目录分隔符,进入目录查找类,此时就找到了

如果运行java命令时,不指定ClassPath参数,需要先进入bin目录,再执行java.exe com.study.java.MainTestClassPath,这样java除了默认查找路径外(这些默认路径和项目文件无关),也会查找当前目录下有没有com.study.java.MainTestClassPath类,同样将.作为目录分隔符,进入目录查找类,此时也能找到

可以尝试将MainTestClassPath.class拿出目录时,不管怎么执行都同样会得到错误: 找不到或无法加载主类的情况,所以默认的ClassLoader的查找是和路径强相关的。

ClassPath

所以ClassPath是什么?

ClassPath是一个环境变量,表示JVM从哪里去寻找要运行的class文件,ClassPath会包含默认路径与执行命令的当前目录

根据javac的生成可以得知,package关键字会将包路径中的.号作为目录分隔符。这样就解释了为什么IDEA中,源码路径也需要保持和package声明保持一致的原因,为了尽量和生成路径一致

IDEA报错

image-20241107161558231

IDEA正常

image-20241107161638350

总结

这篇文章中提到的类加载器的加载阶段,应用程序类加载器 - Application Class Loader是会根据ClassPath来加载类(Bootstrap Class LoaderExtension Class Loader不会),所以做此补充,并且ClassPath可以做为参数传递给JVM,使其包含除默认目录外的其他目录

所以如果你到类找不到了,好好配置一下ClassPath吧

Tips

我们强烈不推荐在系统环境变量中设置classpath,那样会污染整个系统环境。在启动JVM时设置classpath才是推荐的做法。实际上就是给java命令传入-classpath参数:

1
java -classpath .;C:\work\project1\bin;C:\shared abc.xyz.Hello

或者使用-cp的简写:

1
java -cp .;C:\work\project1\bin;C:\shared abc.xyz.Hello

参考

https://liaoxuefeng.com/books/java/oop/basic/package/

https://liaoxuefeng.com/books/java/oop/basic/classpath-jar/index.html

Dan❤Anan
Built with Hugo
主题 StackJimmy 设计