从01开始 从01开始
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)

peterjxl

人生如逆旅,我亦是行人
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)
  • JavaSE

    • 我的Java学习路线
    • 安装Java
    • Java数据类型

    • Java多版本配置
    • 面向对象

      • 枚举类
      • static关键字
      • classpath:JVM查找类的机制
        • 编译一个类
        • 全局classpath
        • 命令行里的classpath
        • 多个classpath
        • 如何编译多个类
        • javac的参数
        • 小结
        • 参考
      • 包:Java组织类的方式
      • jar包
      • class版本
      • 抽象类
      • 接口
      • 访问性修饰符
      • 非访问性修饰符
      • 内部类
    • Java核心类

    • IO

    • Java与时间

    • 异常处理

    • 哈希和加密算法

    • Java8新特性

    • 网络编程

    • Java
  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

  • Java并发

  • Java源码

  • JVM

  • 韩顺平

  • Java
  • Java
  • JavaSE
  • 面向对象
2022-11-26
目录

classpath:JVM查找类的机制

# classpath:JVM查找类的机制

对于刚跨入Java门槛的初学者来说,编译和运行一个简单的类或许很简单,但如果涉及到多个类、类存放的路径不一样、涉及到jar包的时候,就可能稍微复杂了。这里的难点在于对classpath的理解和设置上。本文通过理论与实践相结合的方式,逐步解决编译过程中各种classpath的方式,并有相关的练习和代码。通过本文,读者应该能在不借助IDE的情况下编译和运行代码。

本文环境:Windows10 + Java8。 其他环境也可以运行本博客讲解到的程序(毕竟Java☕ 是跨平台的)

# 编译一个类

还记得我们的第一个Java程序吗?我们新建了一个目录,然后创建了Hello.java文件,编写代码:

public class Hello{
    public static void main(String[] args){
        System.out.println("Welcome to Java!");
    }
}
1
2
3
4
5

我们这里假设新目录为D:\JavaTest

‍

然后我们在文件所在目录,打开命令行,将Java 源文件翻译成 Java字节码文件并执行:

D:\JavaTest> javac Hello.java
D:\JavaTest> java Hello      
Welcome to Java!
1
2
3

‍

我们目前是直接在存放源码的文件夹中进行编译和运行的,一些重要的问题没有暴露出来:

  1. 操作系统如何寻找 javac​ 和 java​命令? 答:由于javac​ 和 java​命令是可执行文件,操作系统会根据我们安装Java的时候配置的path去寻找这两个文件;
  2. 操作系统如何寻找 Hello.java 和 Hello.class文件?此时,就不是根据我们安装Java时的path变量去寻找了,到底如何查找呢?我们一步步测试并分析。

‍

我们将命令行所在目录回退到上一级,此时,当前路径就不是源码存放的路径了,然后我们输入javac​命令:

D:\> javac Hello.java
javac: 找不到文件: Hello.java
用法: javac <options> <source files>
-help 用于列出可能的选项
1
2
3
4

有报错找不到文件,这很正常,因为源码不在当前路径下。我们需要指定一个路径:

D:\> javac D:\JavaTest\Hello.java
1

这回不报错了,编译成功。

‍

‍

接下来我们尝试执行:

D:\> java D:\JavaTest\Hello
错误: 找不到或无法加载主类 D:\JavaTest\Hello
1
2

明明Hello/Hello.class​ 是一个class文件,为什么就找不到了呢?

原来,Java对待​.java​​​文件与​.class​​​文件是有区别的。对.java文件可以直接指定路径给它,而java命令所需的.class文件不能出现扩展名,也不能指定额外的路径给它。请牢记这一点。​

那么,对于Java所需的 .class​文件,如何指定路径呢?必须通过classpath来指定。​

‍classpath是JVM用到的一个环境变量,它用来指示JVM如何搜索class。

# 全局classpath

我们配置一个新的环境变量。首先打开环境变量窗口,然后在用户变量中新建一个变量,变量名为 classpath,变量值为 D:\JavaTest

重新打开一个新的cmd,输入java Hello​

C:\WINDOWS\system32> java Hello
Welcome to Java!
1
2

可以看到,即使我们不在源码所在目录,也能运行Java程序;由此可见,classpath环境变量的作用就是告诉JDK去哪里寻找 .class文件。一旦设置了classpath变量,以后每次运行java或javac命令时,如果要查找.class文件,都会去classpath里找。这是一个全局的设置。

‍

如果有新程序就设置classpath变量,那么也太麻烦了,我们可以在运行Java程序的时候指定一个classpath:

D:\> java -classpath D:/JavaTest Hello
Welcome to Java!
1
2

JDK发现有classpath选项后,就会先根据此classpath指定的路径寻找 .class​文件,找不到再去全局的classpath环境变量中寻找。我们可以试着删除环境变量,打开一个新的CMD,然后再次执行上述命令。

‍

tips: 可以用classpath的缩写cp来代替,如下:

D:\> java -cp D:/JavaTest Hello
Welcome to Java!
1
2

1.特别注意的是,JDK 1.5之前,javac命令不能用cp来代替classpath,而只能用classpath

如果.class​文件路径带有空格,需要加上双引号,例如

javac "D:\Java Test\Hello.java"
java -classpath "D:\Java Test" Hello
1
2

请自行尝试有空格的情况。

‍

‍

‍

‍

‍

‍

‍

‍

# 命令行里的classpath

当我们编译的类里面,包含其他类的时候,该怎么编译呢?我们举个例子:

在Hello.java的同级目录下新建一个Person.java

public class Person {
  private String name;
  public Person (String name){
    this.name = name;
  }

  public String getName() {
    return name;
  }
}
1
2
3
4
5
6
7
8
9
10

修改Hello.java

public class Hello{
  public static void main(String[] args){
    Person person = new Person("Peter");
      System.out.println(person.getName());
  }
}

1
2
3
4
5
6
7

‍

接下来,我们尝试编译

D:\> javac D:/JavaTest/Hello.java
D:\JavaTest\Hello.java:3: 错误: 找不到符号
    Person person = new Person("Peter");
    ^
  符号:   类 Person
  位置: 类 Hello
D:\JavaTest\Hello.java:3: 错误: 找不到符号
    Person person = new Person("Peter");
                        ^
  符号:   类 Person
  位置: 类 Hello
2 个错误
1
2
3
4
5
6
7
8
9
10
11
12

此时我们可以看到JDK报错找不到Person类。为什么之前可以通过指定源码的路径,来编译,现在不行呢?因为之前的Hello类中没有用到其他类,因此JDK不需要去寻找其他类;

而现在,我们用到了Person类,我们就得告诉JDK,去哪里找这个类(即使他们在同一个文件夹下)

D:\> javac -cp D:/JavaTest D:/JavaTest/Hello.java
1

编译通过,可以看到生成了Person.class文件。

实际上由于Hello.java使用了Person.java类,JDK先编译生成了Person.class,然后再编译生成Hello.class。因此,不管Hello.java这个主类使用了多少个其他类,只要编译这个类,JDK就会自动编译其他类,很方便。

我们尝试运行:

D:\> java -cp D:/JavaTest Hello
Peter
1
2

运行成功

‍

# 多个classpath

接下来我们试试,源码在不同目录的情况。

我们在Hello文件夹下新建一个名为lib的文件夹,将Person.java 移动到其下面,并删除Person.class文件。

我们尝试编译:

D:\> javac -cp D:/JavaTest/lib D:/JavaTest/Hello.java
1

编译通过,因为Person在lib文件夹里,我们只需修改classpath为lib文件夹的目录即可

‍

我们尝试运行:

D:\> java -cp D:/JavaTest Hello
Exception in thread "main" java.lang.NoClassDefFoundError: Person
        at Hello.main(Hello.java:3)
Caused by: java.lang.ClassNotFoundException: Person
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        ... 1 more
1
2
3
4
5
6
7
8
9

有报错,因为我们没有指定Person的路径,有报错也是正常的。我们尝试指定:

D:\> java -cp D:/JavaTest/lib Hello
错误: 找不到或无法加载主类 Hello
1
2

还是有报错。。。为什么呢?

由于Java需要在不同文件夹下寻找class文件,而classpath只告诉了JDK一个路径,有错误也是正常的。

在本文的一开始,我们在编译一个类的时候,为什么不用指定classpath也能运行呢?因为默认JVM会从当前路径寻找类,如果指定了classpath,就不从当前路径寻找了。

‍

‍

‍

此时我们需要设置多个classpath,以分号 “;”隔开(注意路径与分号之间不能有空格,Linux上用冒号:分割):

D:\> java -classpath "D:\JavaTest;D:\JavaTest\lib" Hello
Peter
1
2

运行成功,但也暴露出一个问题,如果我们需要用到许多分处于不同文件夹下的类,那这个classpath的设置岂不是很长!有没有办法,对于一个文件夹下的所有.class文件,只指定这个文件夹的classpath,然后让JDK自动搜索此文件夹下面所有相应的路径?有,使用package,且听下回分解。

‍

# 如何编译多个类

当一个包下面很多类的时候,逐个编译是很累的。

比如我们在JavaTest下新建多个类,每个类都有main方法:

public class Hello{
  public static void main(String[] args){
      System.out.println("Hello");
  }
}
1
2
3
4
5
public class Hello2{
  public static void main(String[] args){
      System.out.println("Hello2");
  }
}
1
2
3
4
5
public class Hello3{
  public static void main(String[] args){
      System.out.println("Hello3");
  }
}
1
2
3
4
5

‍

我们尝试编译:

D:\JavaTest> javac Hello.java Hello2.java Hello3.java
1

如果类比较多,每个都要写,容易写错或者写漏。

‍

我们可以用通配符表示编译当前目录所有java文件:

D:\JavaTest> javac *.java
1

‍

也可以指定编译某个目录下的所有Java文件:

 D:\> javac ./JavaTest/*.java
1

‍

那能不能编译子目录下的所有文件呢?比如

javac JavaTest/**/*.java
1

其中,第一个星号表明所有子文件夹,第二个星号表明所有java文件。也就是编译JavaTest目录下所有子文件夹的java代码。很遗憾,在Windows下是不行的,Windows不支持这种通配符,得逐个写出各个类…………但是在Linux下是可以的,笔者试着在Git Bash里也可以用这种通配符,有兴趣的读者请自行尝试

不过一般我们会使用IDE开发,会自动根据包结构编译所有Java源码,无需使用这么复杂的命令;但我们最好还是了解这一点,知道IDE背后帮我们做了什么事情。

‍tomcat,kafka这些大型项目的启动运行,都是在脚本里设置classpath的。

# javac的参数

命令行-d​指定输出的class​文件存放到具体的目录。例如:

D:\> mkdir ./JavaTest/bin
D:\> javac -d ./JavaTest/bin ./JavaTest/*.java
1
2

‍

然后可以看到在bin目录下存放了class文件

D:\JavaTest\bin\Hello.class
D:\JavaTest\bin\Hello2.class
D:\JavaTest\bin\Hello3.class
1
2
3

‍

# 小结

本文所有代码比较简单,也已放到Git上,并补充了相关说明:

Gitee:classpath · 小林/LearnJava - 码云 - 开源中国 (opens new window)

GitHub:classpath at master · Peter-JXL/LearnJava (opens new window)

‍

注意:请自己独立完成上述实验过程,如果有问题再好好看看该博客,以确保确实掌握了classpath的概念和使用。

# 参考

本文主要是参考廖雪峰老师的博客,自己动手实践而得,感谢:

包 - 廖雪峰的官方网站 (opens new window)

classpath和jar - 廖雪峰的官方网站 (opens new window)

Java入门实例classpath及package详解 - 橘子山寨 - BlogJava (opens new window)

在GitHub上编辑此页 (opens new window)
上次更新: 2023/1/23 17:06:18
static关键字
包:Java组织类的方式

← static关键字 包:Java组织类的方式→

Theme by Vdoing | Copyright © 2022-2023 粤ICP备2022067627号-1 粤公网安备 44011302003646号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式