美文网首页程序员
javac和java命令踩坑之路

javac和java命令踩坑之路

作者: 逸先森 | 来源:发表于2018-09-21 17:35 被阅读0次

    相信很多人开发的时候(没错,说的就是我)都是使用IDE进行开发的,而对于命令编译就不是那么熟悉了,今天想练习一下使用命令编译工程,然后就开始了我的踩坑之路......

    在使用命令行编译Java程序的时候,一般会使用到javac进行编译,java执行编译后的程序,如:

    新建工程D:\Desktop\project,然后在project下新建src目录,按照一般习惯,我们继续新建一个com目录,在新建一个test目录,完整的路径如下:

    D:\Desktop\project\src\com\test
    

    在这个目录下新建Test.java文件,编写如下代码:

    public class Test{
    
        public static void main(String[] args) {
            System.out.println("Hello World");
        }
    }
    

    在命令行中输入如下两条命令:

    D:\Desktop\project\src\com\test>javac Test.java
    
    D:\Desktop\project\src\com\test>java Test
    Hello World
    

    执行完javac命令后,目录下除了Test.java文件,还会生成Test.class文件,而java命令执行的就是Test.class文件。

    再做如下测试,回到上两层目录使用javacjava命令:

    D:\Desktop\project\src\com\test>cd ../..
    
    D:\Desktop\project\src>javac com/test/Test.java
    
    D:\Desktop\project\src>java com/test/Test
    错误: 找不到或无法加载主类 com.test.Test
    
    D:\Desktop\project\src>java com.test.Test
    错误: 找不到或无法加载主类 com.test.Test
    

    ???为什么找不到加载主类???黑人问号脸

    这时候得理解一下CLASSPATH环境变量的作用了,在配置JDK环境的时候,通常会加上CLASSPATH的配置:

    .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar;
    

    这里的意思是,.表示当前路径寻找执行的class文件,找不到就去%JAVA_HOME%\lib\dt.jar%JAVA_HOME%\lib\tools.jar包下去找,找到就执行,找不到就报错误: 找不到或无法加载主类。按这么理解,在D:\Desktop\project\src>路径下执行java命令出错是因为classpath路径不对,因为在这个目录下执行javac com/test/Test.java命令,它依然会在com/test/目录下生成Test.class文件,而当前目录没有,所以在当前目录下执行java命令就会报错。

    既然有了方向,那么就可以着手进行解决了,既然是classpath路径有问题,那么执行的时候指定classpath的路径就好了,机智如我。修改编译命令如下,加入-classpath参数:

    D:\Desktop\project\src>java -classpath com/test/ Test
    Hello World
    
    D:\Desktop\project\src>java -classpath com/test com.test.Test
    错误: 找不到或无法加载主类 com.test.Test
    
    D:\Desktop\project\src>java -classpath com/test com/test/Test
    错误: 找不到或无法加载主类 com.test.Test
    

    ???为什么这种操作下还是会有这个错误???难道还是路径的问题???

    我猜,设置classpath后,会与后面跟的class文件做一个路径的拼接,如:

    java -classpath com/test com/test/Test
    

    最后展开的路径是:

    java D:\Desktop\project\src\com\test\com\test\Test
    

    D:\Desktop\project\src\com\test\com\test\这个路径不存在,所以报找不到或无法加载主类的错误,而如下命令:

    java -classpath com/test/ Test
    

    展开后:

    java D:\Desktop\project\src\com\test\Test
    

    classpath的路径是D:\Desktop\project\src\com\test\,这个路径下存在Test.class,所以执行成功。

    这个猜测要如何证明呢?

    容我缓缓思考一下,现在先考虑一下另外一个问题:假如我要让java com.test.Testjava com/test/Test执行成功该如何操作?至今这个操作还没有成功过...

    这时候想想,Java中与路径有关的操作还有哪些?灵机一动,关键字importpackage的作用这时候要显示出来了,我的理解是这样的:

    • package:把.class的文件所在的路径用package表示
    • import:导入这个路径下的class文件

    好像这么说还是有点抽象,举个例子,修改前面的例子,添加package

    package com.test;
    
    public class Test{
    
        public static void main(String[] args) {
            System.out.println("Hello World");
        }
    }
    

    下面,见证奇迹的时候到了,输入以下命令:

    D:\Desktop\project\src>javac com/test/Test.java
    
    D:\Desktop\project\src>java com/test/Test
    Hello World
    
    D:\Desktop\project\src>java com.test.Test
    Hello World
    

    哈哈哈哈哈哈,简直不能更完美,再来一波操作:

    D:\Desktop\project\src>java -classpath com/test Test
    错误: 找不到或无法加载主类 Test
    

    Orz Orz Orz Orz Orz Orz ...苍天呐,到底是为什么啊???难道还是路径问题???再来!!!

    D:\Desktop\project\src>java Test
    错误: 找不到或无法加载主类 Test
    

    .....我去面壁思过两分钟

    ......

    两分钟过去了...

    我回来了,继续

    我们来看一下,加了package和不加package编出来的Test.class有什么区别,class文件可以使用javap命令查看:

    D:\Desktop\project\src\com\test>javap -verbose Test.class
    

    带package的Test.class输出如下:

    Classfile /D:/Desktop/project/src/com/test/Test.class
      Last modified 2018-9-21; size 422 bytes
      MD5 checksum 4464a152af385c705b7b6e4bdf318e66
      Compiled from "Test.java"
    public class com.test.Test
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
       ...
    

    不带package的Test.class输出如下:

    Classfile /D:/Desktop/project/src/com/test/Test.class
      Last modified 2018-9-21; size 413 bytes
      MD5 checksum 0d07e3139e9025ef77df91b7869675e5
      Compiled from "Test.java"
    public class Test
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
       ...
    

    哈哈哈哈哈哈,看到这两个输出,我终于知道问题所在了,优秀!!!

    两个的区别是:

    • 带package:编译后类名为 public class com.test.Test
    • 不带package:编译后类名 public class Test

    这应该就能解释为什么java com.test.Test执行成功,而java Test执行失败了

    回到前面的猜测,classpath配置和class路径的问题,默认的classpath是当前路径,classpath会和class的路径做拼接,如:

    带package的编译:

    # 展开后为D:\Desktop\project\src\com\test
    D:\Desktop\project\src>java com.test.Test
    Hello World
    
    # 展开后为D:\Desktop\project\src\com\test\com\test
    D:\Desktop\project\src>java -classpath com/test Test
    错误: 找不到或无法加载主类 Test
    

    不带package的编译:

    # 展开后为D:\Desktop\project\src\com\test
    D:\Desktop\project\src>java -classpath com/test/ Test
    Hello World
    
    # 展开后为D:\Desktop\project\src\com\test\com\test
    D:\Desktop\project\src>java -classpath com/test com.test.Test
    错误: 找不到或无法加载主类 com.test.Test
    
    # 在D:\Desktop\project\src\com\test路径下执行com.test.Test.class
    # 不存在com.test.Test.class,只有Test.class
    D:\Desktop\project\src\com\test>java com.test.Test
    错误: 找不到或无法加载主类 com.test.Test
    
    # 在D:\Desktop\project\src\com\test路径下执行Test.class
    D:\Desktop\project\src\com\test>java Test
    Hello World
    
    # 在D:\Desktop\project\src路径下执行com.test.Test.class,不存在
    D:\Desktop\project\src>java com.test.Test
    错误: 找不到或无法加载主类 com.test.Test
    

    讲到这里,前面的问题应该都能解释了,不管你们信不信,我就这么理解了

    下面讲一下应用:

    假如我要模仿eclipse编译project,会建多个目录和文件进行编译,src目录放置源码,编译后的class文件编译到classes目录,该如何操作?

    先建好目录,目录结构如下:

    D:\Desktop\project>tree /F
    ├─classes
    │  └─com
    │      └─test
    │          │  Test.class
    │          │
    │          ├─bean
    │          │      Person.class
    │          │
    │          └─utils
    │                  Logger.class
    │
    └─src
        └─com
            └─test
                │  Test.java
                │
                ├─bean
                │      Person.java
                │
                └─utils
                        Logger.java
    

    先看一下Logger.java的内容:

    package com.test.utils;
    
    public class Logger {
    
        public static final boolean ENABLE = true;
    
        public static void e(String message) {
            if (ENABLE) {
                System.err.println(message);
            }
        }
    
        public static void d(String message) {
            if (ENABLE) {
                System.out.println(message);
            }
        }
    }
    

    Person.java的内容:

    package com.test.bean;
    
    public class Person {
    
        public String name;
        public int age;
    
        public Person() {}
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    

    Test.java修改如下:

    package com.test;
    
    import com.test.bean.Person;
    import com.test.utils.Logger;
    
    public class Test{
    
        public static void main(String[] args) {
            Person person = new Person();
            person.name = "Person";
            person.age = 100;
            Logger.d("name is " + person.name);
            Logger.d("age is " + person.age);
        }
    }
    

    接下来要用命令进行编译了,javac命令走起:

    D:\Desktop\project\src>javac -d ../classes D:\Desktop\project\src\com\test\Test.java
    

    -d参数是指定输出目录,目录得先建好,不然会有如下异常:

    D:\Desktop\project\src>javac -d ../classes D:\Desktop\project\src\com\test\Test.java
    javac: 找不到目录: ../classes
    用法: javac <options> <source files>
    -help 用于列出可能的选项
    

    javac执行完了,接着执行java命令:

    D:\Desktop\project\src>java -classpath ../classes com.test.Test
    

    你以为会很顺利么???duang duang duang,想不到吧,运行出现异常了:

    D:\Desktop\project\src>java -classpath ../classes com.test.Test
    Exception in thread "main" java.lang.NoClassDefFoundError: com/test/bean/Person
            at com.test.Test.main(Test.java:9)
    Caused by: java.lang.ClassNotFoundException: com.test.bean.Person
            at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
            at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
            at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
            at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
            ... 1 more
    

    这里就不卖关子了,其实就是Test.java里导入的Person类和Logger类没有参与编译,classes目录下只有com/test/Test.class文件,所以运行的时候发生ClassNotFoundException的异常。

    好吧,瞎扯完了,其实PersonLogger也是有参与编译的,不过生成的目录不是在classes目录下,而是在java文件所在目录:

    D:\Desktop\project\src\com\test\bean\Person.class
    D:\Desktop\project\src\com\test\utils\Logger.class
    

    javac执行的com.test.Test是到如下目录去查找:

    D:\Desktop\project\classes\com\test\bean\Person.class
    D:\Desktop\project\classes\com\test\utils\Logger.class
    

    因为查找不到对应的类文件,所以报了ClassNotFoundException的异常。

    解决方法就是编译的时候把Person.javaLogger.java也加上,如下(忽略每一行后面的斜杠):

    D:\Desktop\project\src>javac -d ../classes D:\Desktop\project\src\com\test\Test.java \
                    D:\Desktop\project\src\com\test\bean\Person.java \
                    D:\Desktop\project\src\com\test\utils\Logger.java
    

    使用相对路径的写法也可以:

    D:\Desktop\project\src>javac -d ../classes com\test\Test.java \
                    com\test\bean\Person.java \
                    com\test\utils\Logger.java
    

    有点长...可以再缩短一点,使用-sourcepath参数进行编译,-sourcepath参数非常适合多文件的编译:

    D:\Desktop\project\src>javac -d ../classes -sourcepath D:\Desktop\project\src \
                    D:\Desktop\project\src\com\test\Test.java
    

    使用相对路径:

    D:\Desktop\project\src>javac -d ../classes -sourcepath D:\Desktop\project\src com\test\Test.java
    

    好吧,还是有点长,不过这不是重点,重点是能运行!!!

    现在运行程序:

    D:\Desktop\project\src>java -classpath ../classes com.test.Test
    name is Person
    age is 100
    

    完美!!!

    假如我要引用第三方jar包该如何操作???

    接下来看我的表演,先制作一个jar包,假设一切很顺利,就是那么自信:

    1. 新建工程test,路径结构如下:
    D:\Desktop\test>tree /F
    ├─classes
    │  │  hello.jar
    │  │
    │  └─com
    │      └─tools
    │              Hello.class
    │
    └─src
        └─com
            └─tools
                    Hello.java
    
    1. Hello.java内容:
    package com.tools;
    
    public class Hello {
    
        public static void say() {
            System.out.println("Hello");
        }
    }
    
    1. 编译Hello.java
    D:\Desktop\test\src>javac -d ../classes D:\Desktop\test\src\com\tools\Hello.java
    
    1. 生成Hello.class进行打包:
    D:\Desktop\test\classes>jar cvf hello.jar com/tools/Hello.class
    

    这样已经生成了hello.jar文件

    1. 拷贝到project工程的libs目录,目录结构如下:
    D:\Desktop\project>tree /F
    ├─classes
    │  └─com
    │      └─test
    │          │  Test.class
    │          │
    │          ├─bean
    │          │      Person.class
    │          │
    │          └─utils
    │                  Logger.class
    │
    ├─libs
    │      hello.jar
    │
    └─src
        │  javac
        │
        └─com
            └─test
                │  Test.java
                │
                ├─bean
                │      Person.java
                │
                └─utils
                        Logger.java
    
    1. 修改Test.java,调用Hello类的say方法:
    package com.test;
    
    import com.test.bean.Person;
    import com.test.utils.Logger;
    import com.tools.Hello;
    
    public class Test{
    
        public static void main(String[] args) {
            Person person = new Person();
            person.name = "Person";
            person.age = 100;
            Logger.d("name is " + person.name);
            Logger.d("age is " + person.age);
            Hello.say();
        }
    }
    
    1. 重新编译Test.java,增加-classpath参数,把hello.jar一同编译:
    javac -d classes -classpath libs/hello.jar -sourcepath src/ src/com/test/Test.java
    
    1. 运行com.test.Test-classpath需要加上hello.jar路径,以;分隔,结果如下:
    D:\Desktop\project>java -classpath classes/;libs/hello.jar com.test.Test
    name is Person
    age is 100
    Hello
    

    优秀!!!

    踩坑之路到此结束,收场,感谢您的观看,我们下回再见~~


    附录:

    关于javac的用法:

    在命令行窗口输入javac命令:

    D:\Desktop\project\src>javac
    

    输出如下:

    用法: javac <options> <source files>
    其中, 可能的选项包括:
      -g                         生成所有调试信息
      -g:none                    不生成任何调试信息
      -g:{lines,vars,source}     只生成某些调试信息
      -nowarn                    不生成任何警告
      -verbose                   输出有关编译器正在执行的操作的消息
      -deprecation               输出使用已过时的 API 的源位置
      -classpath <路径>            指定查找用户类文件和注释处理程序的位置
      -cp <路径>                   指定查找用户类文件和注释处理程序的位置
      -sourcepath <路径>           指定查找输入源文件的位置
      -bootclasspath <路径>        覆盖引导类文件的位置
      -extdirs <目录>              覆盖所安装扩展的位置
      -endorseddirs <目录>         覆盖签名的标准路径的位置
      -proc:{none,only}          控制是否执行注释处理和/或编译。
      -processor <class1>[,<class2>,<class3>...] 要运行的注释处理程序的名称; 绕过默认的搜索进程
      -processorpath <路径>        指定查找注释处理程序的位置
      -parameters                生成元数据以用于方法参数的反射
      -d <目录>                    指定放置生成的类文件的位置
      -s <目录>                    指定放置生成的源文件的位置
      -h <目录>                    指定放置生成的本机标头文件的位置
      -implicit:{none,class}     指定是否为隐式引用文件生成类文件
      -encoding <编码>             指定源文件使用的字符编码
      -source <发行版>              提供与指定发行版的源兼容性
      -target <发行版>              生成特定 VM 版本的类文件
      -profile <配置文件>            请确保使用的 API 在指定的配置文件中可用
      -version                   版本信息
      -help                      输出标准选项的提要
      -A关键字[=值]                  传递给注释处理程序的选项
      -X                         输出非标准选项的提要
      -J<标记>                     直接将 <标记> 传递给运行时系统
      -Werror                    出现警告时终止编译
      @<文件名>                     从文件读取选项和文件名
    

    关于java命令的用法:

    在命令行窗口输入java命令:

    D:\Desktop\project\src>java
    

    输出如下:

    用法: java [-options] class [args...]
               (执行类)
       或  java [-options] -jar jarfile [args...]
               (执行 jar 文件)
    其中选项包括:
        -d32          使用 32 位数据模型 (如果可用)
        -d64          使用 64 位数据模型 (如果可用)
        -server       选择 "server" VM
                      默认 VM 是 server.
    
        -cp <目录和 zip/jar 文件的类搜索路径>
        -classpath <目录和 zip/jar 文件的类搜索路径>
                      用 ; 分隔的目录, JAR 档案
                      和 ZIP 档案列表, 用于搜索类文件。
        -D<名称>=<值>
                      设置系统属性
        -verbose:[class|gc|jni]
                      启用详细输出
        -version      输出产品版本并退出
        -version:<值>
                      警告: 此功能已过时, 将在
                      未来发行版中删除。
                      需要指定的版本才能运行
        -showversion  输出产品版本并继续
        -jre-restrict-search | -no-jre-restrict-search
                      警告: 此功能已过时, 将在
                      未来发行版中删除。
                      在版本搜索中包括/排除用户专用 JRE
        -? -help      输出此帮助消息
        -X            输出非标准选项的帮助
        -ea[:<packagename>...|:<classname>]
        -enableassertions[:<packagename>...|:<classname>]
                      按指定的粒度启用断言
        -da[:<packagename>...|:<classname>]
        -disableassertions[:<packagename>...|:<classname>]
                      禁用具有指定粒度的断言
        -esa | -enablesystemassertions
                      启用系统断言
        -dsa | -disablesystemassertions
                      禁用系统断言
        -agentlib:<libname>[=<选项>]
                      加载本机代理库 <libname>, 例如 -agentlib:hprof
                      另请参阅 -agentlib:jdwp=help 和 -agentlib:hprof=help
        -agentpath:<pathname>[=<选项>]
                      按完整路径名加载本机代理库
        -javaagent:<jarpath>[=<选项>]
                      加载 Java 编程语言代理, 请参阅 java.lang.instrument
        -splash:<imagepath>
                      使用指定的图像显示启动屏幕
    有关详细信息, 请参阅 http://www.oracle.com/technetwork/java/javase/documentation/index.html。
    

    相关文章

      网友评论

        本文标题:javac和java命令踩坑之路

        本文链接:https://www.haomeiwen.com/subject/tyqznftx.html