- 编译执行单个不带包名的Java文件
- 编译执行单个带包名的Java文件
- 指定编译生成字节码文件的位置
- 编译执行多个Java文件
- 打包无MANIFEST的jar文件
- 打包多个文件并指定MANIFEST
- 编译引用了第三方jar文件的项目
- 打包引用了第三方jar文件的项目为jar文件
下载代码:https://github.com/woojean/demos/tree/master/java-cmd-compile
不使用Maven、Gradle等构件工具,直接使用命令行来构建Java项目,加深对Java编译打包过程的理解。
编译执行单个不带包名的Java文件
如果没有指定包名,所有的示例都属于一个默认的无名包。
使用默认编译行为
javademo/src/First.java
public class First{
public static void main(String args[]){
System.out.println("First");
}
}
注意:package声明不是必须的。
编译:
javac src/First.java // 将在src目录下(源代码相同目录下)生产字节码文件First.class
运行:
cd src
java First // 之所以这样可以运行,是因为在CLASSPAHT中配置了.,这样java在执行时会在当前目录下寻找字节码文件
输出:
First
在运行时添加CLASSPATH
如果这样执行:
java src/First
将会报错:
错误: 找不到或无法加载主类 src.First
当然也可以在执行Java时临时指定CLASSPATH:
java -cp src/ First // 将src目录临时添加到CLASSPATH中
输出:
First
编译执行单个带包名的Java文件
javademo/src/First.java
package com.javademo;
public class First{
public static void main(String args[]){
System.out.println("First");
}
}
编译:
javac src/First.java
无法执行:
java -cp src/ First // 错误: 找不到或无法加载主类 First
java -cp src/ com.javademo.First // 错误: 找不到或无法加载主类 First
cd src
java First // 错误: 找不到或无法加载主类 First
java com.javademo.First // 错误: 找不到或无法加载主类 First
Java包名必须是文件所在实际物理路径(将文件分隔符替换为.后)的一部分。 而为了保证编译后的Java字节码能够被执行(能够被Java虚拟机找到),该物理路径的前缀部分必须添加到CLASSPATH中(可以写配置文件,也可以在运行时指定,IDE就是在运行时添加)。
因此修改目录结构为:
src/com/javademo/First.java
编译:
javac src/com/javademo/First.java
执行:
java com.javademo.First // OK!
java com/javademo/First // OK!
注意如下执行是错误的:
java src/com/javademo/First // 错误: 找不到或无法加载主类 src.com.javademo.First,因为src并不属于包名的一部分
java -cp src/com/javademo com.javademo.First // 错误: 找不到或无法加载主类 com.javademo.First
即,使用java执行带包名的类时,会在当前路径下,以包名结构为路径结构来寻找待执行的字节码文件。 因此在src目录下执行如下命令:
java com.javademo.First // OK!
因为当前目录(.)被添加到系统的CLASSPATH中,所以可以在当前目录(/Users/wujian/projects/javademo/src
)下寻找字节码文件。
又因为com.javademo.First指定了包名,对应解析出来的类字节码位置为com/javademo/First
,所以最终定位字节码文件的路径为:
/Users/wujian/projects/javademo/src/com/javademo/First
。
因此,在src父目录执行:
java src/com/javademo/First
实际拼凑的字节码定位路径为:/Users/wujian/projects/javademo/src/com/javademo/First
,路劲虽然是正确的,但是指定的类(src.com.javademo.First
)却是不存在的。
而如下的执行:
java -cp src/com/javademo com.javademo.First
根据该指定CLASSPATH实际拼凑的字节码定位路径为:/Users/wujian/projects/javademo/src/com/javademo/com/javademo/First
,显然错误。
总结一下寻找字节码的行为就是: 对于要运行的带包名的类,先按包名转换得到一段相对路径,再结合所有的CLASSPATH,拼凑CLASSPATH和包名相对路径得到要查找类的定位位置,然后再在该位置处查找与所需带包名的类(完全限定名)匹配的字节码。
寻找字节码时依赖字节码文件的名称
mv com/javademo/First.class com/javademo/Second.class
java com.javademo.First // 错误: 找不到或无法加载主类 com.javademo.First
指定编译生成字节码文件的位置
编译
javac src/com/javademo/First.java -d target/
执行
cd target/
java com.javademo.First // OK!
编译执行多个Java文件
javademo/src/First.java
package com.javademo;
public class First{
public String output(){
return "First";
}
public static void main(String args[]){
System.out.println("First");
}
}
javademo/src/Second.java
package com.javademo;
public class Second {
public static String output() {
First first = new First();
String firstOutput = first.output();
return firstOutput + "->" + "Second";
}
public static void main(String args[]) {
System.out.println(Second.output());
}
}
编译
javac src/com/javademo/First.java src/com/javademo/Second.java -d target/
执行
cd target/
java com.javademo.Second // First->Second
如果分别单独编译将报错:
javac src/com/javademo/First.java -d target/ // OK!
javac src/com/javademo/Second.java -d target/
编译Second.java时报错:
src/com/javademo/Second.java:6: 错误: 找不到符号
First first = new First();
^
符号: 类 First
位置: 类 Second
不过,一起编译时,文件列出的顺序并不依赖于类之间的引用关系:
javac src/com/javademo/Second.java src/com/javademo/First.java -d target/ // Second写在First前面 OK!
打包无MANIFEST的jar文件
将First类打包为一个jar文件。
cd target/
jar cvf First.jar com/javademo/First.class
输出:
已添加清单
正在添加: com/javademo/First.class(输入 = 495) (输出 = 314)(压缩了 36%)
执行:
java -cp First.jar com.javademo.First // OK! 注意-cp选项
MANIFEST.MF的内容
Manifest-Version: 1.0
Created-By: 1.8.0_121 (Oracle Corporation)
打包多个文件并指定MANIFEST
vi mymanifest
Manifest-Version: 1.0
Created-By: 1.8.0_121 (Oracle Corporation)
Main-Class: com.javademo.Second
打包:
jar cvfm Second.jar mymanifest com/javademo/First.class com/javademo/Second.class
执行:
java -jar Second.jar // OK! 无需指定要执行的类
编译引用了第三方jar文件的项目
创建第三方jar文件
src/com/thirdparty/Third.java
package com.thirdparty;
public class Third {
public String output() {
return "Third";
}
public static void main(String args[]) {
System.out.println("Third");
}
}
javac src/com/thirdparty/Third.java -d target/
cd target/
jar cvf Third.jar com/thirdparty/Third.class
在项目中引用第三方jar包
jar包位置:
lib/Third.jar
代码中的引用:
package com.javademo;
import com.thirdparty.Third;
public class Fourth {
public static String output() {
Third third = new Third();
String thirdOutput = third.output();
return thirdOutput + "->" + "Fourth";
}
public static void main(String args[]) {
System.out.println(Fourth.output());
}
}
编译
javac -cp lib/Third.jar -d target/ src/com/javademo/Fourth.java
运行
java com.javademo.Fourth // Third->Fourth
打包引用了第三方jar文件的项目为jar文件
创建lib目录,并将要引用的jar文件放到lib目录中:
mkdir lib
cp Third.jar lib/Third.jar
vi mymanifest
Manifest-Version: 1.0
Created-By: 1.8.0_121 (Oracle Corporation)
Main-Class: com.javademo.Fourth
Class-Path: lib/Third.jar
Class-Path可以有多个,用空格分割:
Class-Path: lib/commons-codec.jar lib/commons-httpclient-3.1.jar lib/commons-logging-1.1.jar
java执行程序只会到目录下寻找.class文件,而不会解压目录下的jar文件然后再在解压后的内容中寻找.class文件。所以,对于jar包的引用要指定路径。 所以这里Class-Path的行为和CLASSPATH的行为是不一样的。
打包:
cd target/
jar cvfm Fourth.jar mymanifest com/javademo/Fourth.class
运行:
java -jar Fourth.jar
输出:
Exception in thread "main" java.lang.NoClassDefFoundError: com/thirdparty/Third
at com.javademo.Fourth.output(Fourth.java:8)
at com.javademo.Fourth.main(Fourth.java:15)
Caused by: java.lang.ClassNotFoundException: com.thirdparty.Third
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 2 more
原因在于在当前执行路径下没有lib/First.jar
.
cd target/
mkdir lib
cp Third.jar lib/Third.jar
运行:
java -jar Fourth.jar -cp lib/ // Third->Fourth