前言:基础不牢,地动山摇。从开始学习语言,就使用IDE集成开发工具编写代码,一个按钮,就可以解决编译和运行的所有问题,就像是汽车中的自动档一样。它隐藏了许多重要的细节,其实开发工具编译、运行使用的就是JDK中的javac和java。我们也可以直接使用这些工具来编译和运行我们的代码,期间,根定会遇到很多有趣的问题,解决这些问题,有助于我们了解本质和提升能力。
一 问题发生
如下图所示,使用编译工具javac编译源文件时发生了问题:程序包XXX不存在;找不到符号,主要是类名和方法名找不到。
二 问题解决
原因可能有三个。如果只是出现了问题“错误:找不到符号”,那么原因可能是你没有在源文件中通过import关键字导入该类;如果同时出现问题“程序包XXX不存在”,找不到依赖的文件。如果不是以上两个原因,那可能是你源文件中的类名书写有误。
1. 未使用import关键字导入该类
解决:导入即可,例如import javax.servlet.*;
2. 使用时类名书写错误
解决:检查你在源文件中使用的类名或者方法名是否和导入的类一致,不一致则修改。
3. 未找到依赖的类
在介绍前,我们先来了解一些专业术语和javac命令的使用方法。
如下图所示,我了方便说明,我新建了一个helloworld项目,类MyApp依赖了两个类,分别是自定义的pany.util.Say
和来自第三发jar包的edu.princeton.cs.algs4.StdOut
,调用的函数都是简单的打印输出。
MyApp.java
package pany.app;import edu.princeton.cs.algs4.StdOut;import pany.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 pany.util;public class Say{public static void print(String words){System.out.println(words);}}
helloworld为项目根目录,src为源文件根目录,out\classes为源文件编译后输出的根目录.
全限定类名:包名+类名,一个类的包名体现在操作系统中是对应的目录,例如,MyApp.java 定义了package pany.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 pany.util.Say
,先去classpath下查找,由于没有配置环境变量或显示的使用-classpath参数,所以在当前目录helloworld\src下,查找pany.util.Say(对应于文件系统,com\compnay\util\Say.class)显然不存在。然后,去sourcepath(由于没有指定-sourcepath参数,所以sourcepath为当前目录,即helloworld\src)下查找pany.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存在pany.util.Say.class文件,sourcepath的src存在pany.util.Say.java文件,在编译时,会检查Say.java在编译后是否存在更新,如果有更新,则则先编译,然后使用out\classes下的pany.util.Say.class文件,如果不存在更新,则直接使用。
总结
注:-d中目录必须先手动创建
三 其他
欢迎阅读,其他相关文章:
Java命令行运行错误: 找不到或无法加载主类javac 错误: 编码GBK的不可映射字符