javac和java命令踩坑之路

相信很多人开发的时候(没错,说的就是我)都是使用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。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,475评论 4 372
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,744评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,101评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,732评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,141评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,049评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,188评论 2 320
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,965评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,716评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,867评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,341评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,663评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,376评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,200评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,990评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,179评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,979评论 2 279

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,112评论 18 139
  • 精心收集整理的教程笔记 第01天 Java基础入门 第1章计算机基础 1.1计算机 计算机(computer)俗称...
    Java帮帮阅读 5,901评论 1 109
  • 一、JDK的安装 1、Java-Package choose 上述文档翻译: JDK:针对Java开发者(Java...
    7叶阅读 1,316评论 1 2
  • 姓名:陈国林 公司:宁波大发化纤有限公司 组别:宁波盛和塾第235期利他3组 日精打卡时间:2017-06-02 ...
    陈国林阅读 214评论 0 0
  • 。[********.️ 你说:"前半生走马观花睡姑娘 反正你也不相信感情 干脆后半生就住在我的床上 能做就做一场...
    王子寒_30d4阅读 141评论 0 0