我们一般都是通过IDE(如Eclipse、Intellij Idea,STS等)来开发,调试java项目。
在不借助IDE的情况下,如何编译、运行Java程序。
使用javac 命令,可以通过只敲击javac 看到各种命令参数。
必学参数 -d -cp,这俩下面会讲到
如果javac命令不能用,看一下环境变量是否没配对。
我们从简单到复杂来看java编译、运行命令
单独类如何编译
我们可以用ide(eclipse、idea,甚至高级点的文本编辑工具Emeditor、Notepad++、UE)准备java文件,然后拷贝到硬盘,比如D盘。javac命令需要带.java后缀名,执行java文件不需要带后缀名。
一、不带包名的类如何编译
1、没有中文的java文件。
准备代码
public class A {public static void main(String[] args) {System.out.println("abc");}}
命令:
javac A.java
java A
输出结果
2、假设输出的是中文,会怎么样?
修改代码与输出结果,两种情况与两种结果
a)
public class A {public static void main(String[] args) {System.out.println("你好吗");}}
错误:编码GBK的不可映射字符
b)
public class A {public static void main(String[] args) {System.out.println("你好");}}
能编译成功,但是输出乱码
原因分析:
我们需要了解javac和java命令是什么样的过程。
javac是java compiler的命令,是将.java文件编译成.class文件的过程。我们需要先将文件读入内存,才能进行编译。
读入内存需要知道文件的编码格式,才能正确的将文件读取。我们查看一下java源文件的编码,发现是UTF-8。而java编译器默认的字符集可以通过如下代码查看。
System.out.println(Charset.defaultCharset());
会发现输出GBK。
也就是java编译器认为文件采用GBK编码,而实际上文件是采用UTF-8编码。然后“你好吗”三个字的UTF-8码值,转换成GBK就是"浣犲ソ鍚�",这个问号“�”就是一个GBK不可映射的字符。可以用下面的代码试一下:
String s = "你好吗";String s1 = new String(s.getBytes("utf-8"), "gbk");System.out.println(s1);
而如果是"你好"两个字的UTF-8码值转换成GBK是这三个字符"浣犲ソ"。
String s = "你好";String s1 = new String(s.getBytes("utf-8"), "gbk");System.out.println(s1);
编译器使用了UTF-8的二进制值来尝试转换成GBK,第一次认识到了一个不认识的字符,因为UTF-8的范围很大,这个码值在GBK中没有,就报了这个错。
而第二次编译通过,是因为“你好”这两个字的UTF-8编码,恰好能转换成GBK编码,所以能编译通过。但是编译通过并不保证内容就是正确的,输出的时候仍然是乱码。
问题:
为什么我们通过IDE就能编译通过。
通过IDE,不可能分开java文件编码和java compiler的编码格式的,文件设置成什么编码,编译器都会知道,就会用什么编码来解析。原生的javac不会这样,它只会按照默认的系统编码来编,这个时候如果文件编码不同,就出现这个问题了。
知道了原因,怎么解决,两种解决方案,最终目的是为了文件编码和解码字符集相同
1)如果文件是UTF-8编码的,我们使用-encoding UTF-8 来显式指定为UTF-8的编码格式。
2)将文件改为GBK编码,如果使用windows自带的记事本,保存为ANSI,中国区域会使用GBK编码。如果使用其它高级文本编辑工具,如:notepad++、Emeditor、UE这样的,另存为指定格式。
然后再编译运行就可以了。
这里的GB2312(936)就是GBK,不是GB2312那个阉割版。
二、带包名的类如何编译
准备代码,我们已经讨论过中文乱码问题了,为了简单起见,下面都用英文示范其它的情况。
package mypack;public class A {public static void main(String[] args) {System.out.println("abc");}}
编译运行:
我们会发现编译成功,A.class被编译到了D盘根目录下。运行报错“错误:找不到或无法加载主类A”
原因分析:
这里地方有点绕人,我们先分析为什么现在的命令不行。
java A
有包的java程序,需要用完整包名来执行
由于我们没有指定classpath,jvm准备在当前路径下查找A.class来装载,找了一圈没找到(确实有个A类,但是A类的完整路径是mypack.A,所以不是这个),报错找不到或无法加载主类。
java mypack.A
有包的java程序,文件路径中必须包含包名,并以包名结尾
jvm看了一下有包,于是将包转换为路径,也就是期望在D:/mypack文件夹下,找到A.class文件进行装载。也没找到。
如果有包,java命令必须在包的上层目录执行完整路径名(完全限定名),上例中A.class的完全限定名是mypack.A。如果在D盘下,有一个A.java,包路径为c,必须在D盘下,执行java c.A才行,此处的“在D盘下”,暂时可以看做直接在D盘下,也可以通过-cp指定到D盘下,这个后面还会说。
解决办法
我们可以使用-d . 来让编译器以当前路径为基准,自动创建包路径,这个-d .放在前面,放在后面都可以
这个-d 可以将文件编译到指定目录下。
假设我们在D盘下创建一个aa的目录,然后执行javac -d aa A.java,效果如下。
使用-classpath指定包的上级目录
使用-classpath(或者 -cp,简写,意思相同)指定.class的目录,使用相对路径,绝对路径都可以,这个目录直接通到mypack的上级即可。
我们可以通过-classpath指定.class在哪个根目录下,然后从这个目录拼接上包路径来构成完整路径。
javac的自由性
javac命令使用了可指定编译路径的可选项(option),可以指定不指定,不指定将在当前目录生成.class文件;可以指定为-d . ,将会在当前目录下创建包的全路径。可以指定位-d xx/xxx/xxxx 具体的目录,将会在具体目录下创建包的全路径。
这几种命令产生的.class文件本身完全相同。
等于并不限定.class文件产生的位置,因为javac只是创建。可能java文件本身可能就不放在目标位置。也可能创建的.class文件通过网路传输到别的地方再执行,所以决定了由使用者自己来决定放到什么地方。
三、引入其它自定义类
1、引入非自定义类
假设我们编辑如下的代码:
import java.nio.charset.Charset;public class A {public static void main(String[] args) {System.out.println(Charset.defaultCharset());}}
这个先放这边,我们举另外一个例子
2、引入自定义类
准备一个非默认的包,java目前版本不允许引入未命名包的类。让A引用B。
package pack;public class B {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}public void showMyName(){System.out.println("my name is " + this.getName());}}
package mypack;import pack.B;public class A {public static void main(String[] args) {B b = new B();b.setName("abc");b.showMyName();}}
然后将A和B都放入D盘根目录,使用javac -d . 编译A.java
解决方案一:
我们可以用比较无脑的方式
甚至,可以javac -d . *.java,但是我认为.* 不妥,这样把不必要的类也编译了。
解决方案二:
首先,java程序会将被引用的类也打包的。
然后,如果类是相互引用并且不同包的,一定要按照包的路径放好,保持包定义和文件结构同步。
我们新建mypack和pack目录,将A.java丢到mypack目录下,将B.java丢到pack下。
如果B.java在其它路径:
上面的例子隐含了一种情况,A.java和B.java的包都在敲击命令目录的下级目录,包路径和隐含的classpath(也就是当前路径)构成了完整的路径。假设B在其它路径怎么办?
我们将B.java丢到E盘下的aa目录。这个时候只需要使用-cp指定B.java的上级路径即可。
我们打开E:\aa文件夹查看,会发现B.class并不在这里,因为B.java只是一个source路径而已。最终的.class文件仍然是相对当前敲击命令的位置安放。
如果B.class在其它路径。
我们将B.class也移动到E:\aa目录试试
好熟悉的报错。哈哈
这个时候需要使用-cp,但是看以第一条命令,使用-cp只指定了一个目录,会认为mypack.A也在这个路径下,要分开指定,使用"."代表当前路径,使用分号隔开多个class路径。
总结一下:
1、如果有中文乱码,考虑是文件的编码方式和javac的编码方式不同。
javac的编码方式默认是ANSI的,也就是不同区域不同编码,中国区是GBK。可以使用Charset.defaultCharset()来查看。
将java文件编码和javac解码字符集统一。
a)修改java文件编码。
b)使用-encoding指定javac编译时候使用的编码。
2、对于有包的java程序,执行的时候要在包路径的上级路径,使用带有包路径的全限定名来执行。
包路径包含于实际文件路径,并且是实际文件路径的后面部分,当然特殊情况可以和文件路径相同。使用classpath指定包的上级目录,来执行不在当前路径下的java文件。
3、javac 有个可选参数 -d,如果不填会将.class文件放到当前目录,并且不带包名;如果填了会将.class文件放到指定目录,并且会创建包路径,可以用-d . 会在当前目录创建包路径。
4、javac和java都可以使用-cp/-classpath来操作执行路径下的文件。classpath可以有多个值,使用分号隔开,如果是.,表示当前目录。jvm会扫描classpath的所有目录,从中查找源文件和可执行文件。
最后吐槽一下CSDN这个,,,我从有道云拷贝过来,图片要一个一个贴,不知道是哪边的锅,累死我了