前言:基础不牢,地动山摇。从开始学习语言,就使用IDE集成开发工具编写代码,一个按钮,就可以解决编译和运行的所有问题,就像是汽车中的自动档一样。它隐藏了许多重要的细节,其实开发工具编译、运行使用的就是JDK中的javac和java。我们也可以直接使用这些工具来编译和运行我们的代码,期间,根定会遇到很多有趣的问题,解决这些问题,有助于我们了解本质和提升能力。

一 问题发生

  如下图所示,使用编译工具javac编译源文件时发生了问题:程序包XXX不存在;找不到符号,主要是类名和方法名找不到。

在这里插入图片描述

二 问题解决

  原因可能有三个。如果只是出现了问题“错误:找不到符号”,那么原因可能是你没有在源文件中通过import关键字导入该类;如果同时出现问题“程序包XXX不存在”,找不到依赖的文件。如果不是以上两个原因,那可能是你源文件中的类名书写有误。

1. 未使用import关键字导入该类

解决:导入即可,例如import javax.servlet.*;

2. 使用时类名书写错误

解决:检查你在源文件中使用的类名或者方法名是否和导入的类一致,不一致则修改。

3. 未找到依赖的类

在介绍前,我们先来了解一些专业术语和javac命令的使用方法。

如下图所示,我了方便说明,我新建了一个helloworld项目,类MyApp依赖了两个类,分别是自定义的com.company.util.Say和来自第三发jar包的edu.princeton.cs.algs4.StdOut,调用的函数都是简单的打印输出。
在这里插入图片描述
MyApp.java

package com.company.app;

import edu.princeton.cs.algs4.StdOut;
import com.company.util.Say;

public class MyApp {
	public static void main(String args[]) {
		
		StdOut.print("The third party lib: Hello World!");
		
		Say.print("My own lib: Hello World!");
	}
}

Say.java

package com.company.util;

public class Say{
	
	public static void print(String words){
		System.out.println(words);
	}
}

helloworld为项目根目录,src为源文件根目录,out\classes为源文件编译后输出的根目录.

全限定类名:包名+类名,一个类的包名体现在操作系统中是对应的目录,例如,MyApp.java 定义了package com.company.app; 所以MyApp.java应该在com\company\app目录下,这部分目录结构应该看成是和MyApp.java文件是一体的。理解这一点很重要,javac和java查找类都是根据全限定类名的

还有在下文提到的当前目录,即命令运行时所在的目录。例如,运行命令C:\Users\gzn\helloworld>javac src\com\company\app\MyApp.java 其中“C:\Users\gzn\helloworld”就是当前目录。


javac命令的使用方法

javac <options> <source files>

<options>:
-classpath (等同-cp)<路径> 指定查找用户类文件和注释处理程序的位置件所,默认当前目录,即命令运行时所在目录
-sourcepath <路径> 指定查找输入源文件的位置,默认当前目录,即命令运行时所在目录
-d <目录> 指定放置生成的类文件的位置,默认源文件所在位置

<source files>
需要编译的源文件

注意:jdk中的函数库lib称为Java的标准库,指定了JAVA_HOME环境变量后就可以使用了,编译和运行会自动在相应位置查找依赖的类。而第三方库(如mysql-connector-java-5.1.40.jar)和用户自己定义的类库 在编译和运行时,需要在-cp类加载路径参数中指明库的位置。


下面将通过错误在现的方式,一步步得出解决问题的最佳实践。

第一步,在项目根目录helloworld下运行编译命令,发现自定义和第三方的程序包都没有找到,进而其包中的类也找不到,所以下面也出现了错误:找不到符号。
在这里插入图片描述
我们先来解决找不到第三方程序包edu.princeton.cs.algs4的问题,由于我们在运行javac命令时,没有通过参数-classpath指定字节码类文件(后缀.class)的查找路径,因此会使用操作系统环境变量中定义的CLASSPATH(如果定义了的话)。我们先假设没有定义该环境变量,那么就要从当前目录下查找字节码类文件,即从helloworld\目录下查找edu
princeton.cs.algs4.StdOut(对应与操作即,edu\princeton\cs\algs4\StdOut.class),可想而知,根本不存在该目录及文件。我们在回过头来看看操作系统的环境遍量,如下图所示
在这里插入图片描述

变量值中的点“.”代表当前目录,我们已经得知,当前目录下找不到程序包。接着回去%JAVA_HOME\lib\dt.jar和%JAVA_HOME\lib\tool.jar中寻找程序包edu.princeton.cs.algs4,显然也是查找不到的,因为此程序包位于helloworld\libs\algs4.jar中。所以,可以把该路径加入到系统环境变量中,但是此方法太麻烦,不可能每次添加依赖都去设置一下环境变量,因此不推荐使用。那么可以通过使用-classpath参数设置程序包所在的jar,此方法是官方推荐的。

第二步,在项目根目录helloworld下运行编译命令,并使用-classpath参数
在这里插入图片描述
我们自定义的类的程序包不存在,原因有二:1.源文件还没有编译 2.编译后的字节码文件未添加到类查找路径

第三步,在helloworld\src源文件根目录下运行编译命令”

在这里插入图片描述
可以看到我们自定义的程序包即使用的类不再有问题,但是第三方程序包又找不到了。我们先来分析为什么自定义的程序包及类没有问题,编译时,遇到语句import com.company.util.Say,先去classpath下查找,由于没有配置环境变量或显示的使用-classpath参数,所以在当前目录helloworld\src下,查找com.company.util.Say(对应于文件系统,com\compnay\util\Say.class)显然不存在。然后,去sourcepath(由于没有指定-sourcepath参数,所以sourcepath为当前目录,即helloworld\src)下查找com.company.util.Say(对应于文件系统,com\compnay\util\Say.java),可以查找到,于是就进行了编译。由于没有指定编译输出目录参数-d,因此,编译后的字节码文件放在了与源文件相同的目录下,即com\compnay\util\Say.class。然后,再重新在classapth下(即当前目录helloworld\src下)就可以找到程序包及类文件了。

对于没有依赖第三发程序包的情况,在源文件根目录下运行编译命令,不会出现题目中的问题。

但是,我们依赖的第三方程序包及类文件,由于没有通过参数-classpath指定,又找不到了,因此,我们需要在-classpath参数中指定当前目录和第三方程序包及类所在目录。

第四步,在“helloworld\src源文件根目录”下运行编译命令,并使用-classpath参数
命令:C:\Users\gzn\helloworld\src>javac -classpath .;C:\Users\gzn\helloworld\libs\algs4.jar com\company\app\MyApp.java (图片不清数,点击图片可以使图片变清晰,或者看命令!)
在这里插入图片描述

其实,第四步还可以优化,我们希望helloworld\src源文件下只存在源文件,即编译输出到一个固定的目录,如out\classes,不在输出到默认的目录,这样可以使得项目的结构根清晰,同时classpath中的当前目录应该替换为out\classes,还需要使用-\sourcepath指定源文件根目录,不在使用当前目录为源文件根目录。

第四部优化,同时是最佳实践,在helloworld\src源文件根目录,下运行命令,并使用-classpath参数

命令:C:\Users\gzn\helloworld>javac -sourcepath src -classpath out\classes;libs\algs4.jar -d out\classes src\com\company\app\MyApp.java(图片不清数,点击图片可以使图片变清晰,或者看命令!)
在这里插入图片描述
对于同时依赖自定义和第三方类文件的情况,推荐建议使用这种方法和类似的目录结果。

另外补充说明,现在classpath的out\classes存在com.company.util.Say.class文件,sourcepath的src存在com.company.util.Say.java文件,在编译时,会检查Say.java在编译后是否存在更新,如果有更新,则则先编译,然后使用out\classes下的com.company.util.Say.class文件,如果不存在更新,则直接使用。

总结

情况解决方式
依赖自定义的类在源文件根目录下运行命令 ,例如C:\Users\gzn\helloworld\src>javac com\company\app\MyApp.java
依赖自定义类和第三发jar包在源文件目录下运行命令,使用classpath参数,使用默认sourcepath源文件路径,默认d编译输出路径,即当前路径,例如C:\Users\gzn\helloworld\src>javac -classpath .;C:\Users\gzn\helloworld\libs\algs4.jar com\company\app\MyApp.java
依赖自定义类和第三方包优化最佳实践在源文件目录下运行命令,使用classpath参数,使用sourcepath参数,使用d参数,例如C:\Users\gzn\helloworld>javac -sourcepath src -classpath out\classes;libs\algs4.jar -d out\classes src\com\company\app\MyApp.java

注:-d中目录必须先手动创建


三 其他

欢迎阅读,其他相关文章:

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐