相信很多人开发的时候(没错,说的就是我)都是使用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
文件。
再做如下测试,回到上两层目录使用javac
和java
命令:
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.Test
或java com/test/Test
执行成功该如何操作?至今这个操作还没有成功过...
这时候想想,Java中与路径有关的操作还有哪些?灵机一动,关键字import
和package
的作用这时候要显示出来了,我的理解是这样的:
- 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
的异常。
好吧,瞎扯完了,其实Person
和Logger
也是有参与编译的,不过生成的目录不是在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.java
和Logger.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包,假设一切很顺利,就是那么自信:
- 新建工程test,路径结构如下:
D:\Desktop\test>tree /F
├─classes
│ │ hello.jar
│ │
│ └─com
│ └─tools
│ Hello.class
│
└─src
└─com
└─tools
Hello.java
-
Hello.java
内容:
package com.tools;
public class Hello {
public static void say() {
System.out.println("Hello");
}
}
- 编译
Hello.java
:
D:\Desktop\test\src>javac -d ../classes D:\Desktop\test\src\com\tools\Hello.java
- 生成
Hello.class
进行打包:
D:\Desktop\test\classes>jar cvf hello.jar com/tools/Hello.class
这样已经生成了hello.jar
文件
- 拷贝到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
- 修改
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();
}
}
- 重新编译
Test.java
,增加-classpath
参数,把hello.jar
一同编译:
javac -d classes -classpath libs/hello.jar -sourcepath src/ src/com/test/Test.java
- 运行
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。
网友评论