在编译的java源文件(.java)时,编译器会根据环境变量或者javac的-cp属性指出的参数建立类路径引用表。其中环境变量和-cp参数不会叠加,如果指定了cp参数,环境变量将失效。如果两者都没有,则默认以当前目录(.)为类引用表的路径.
如果源文件中有import指令,则会检查类引用表中有没有指定的目录。如果没有则会报错,如果有,则会根据import里的是具体的类,则会建立类引用表,如果导入的是整个包(*),则会建立相对类引用表。
有了以上三张表,在编译的源文件时,出现的类会有下面几种情况:
1)类的全名。直接在类引用表中查找这个类,如果存在源文件,而不存在*.class类文件,就对源文件进行编译(这就是类似make的机制);如果存在类文件,就继续编译。如果类引用表中没有这个类,则会对相对类引用表中相应的项与类路径引用表组合,查找这个类。如果找到源文件还要对其进行编译。
2)只是单独的一个类名。则会搜索类路径引用表下的存在相对类引用表中路径的类,如果找到源文件,进行编译,找到类文件继续编译,找不到报错。
使用相对类引用表,也有人称为按需导入,只有使用到的类才会用到,但是在搜索的时候会把所有可能的路径都进行搜索,以确定是否有类冲突。对于使用效率,说法不一,《java深度历险》一书上说,java有相应的算法,不会影响到使用效率,而在网上看到的一些资料,说会影响效率。不知道哪一种是对的,但是有一点是确定的,使用按需导入,命名冲突是有可能发生的。
在编译之后的class文件中,已经没有了import和package,所以的类都以类全名出现。
java的import机制与C++中的#include是不同的,而是相当于namespace。因为import导入的是路径,不会递归,所以只能使用一个*作为通配符,而不能使用import some.*.*;
如果一个类文件中写了package,如:
package hk;
public class HK {
public static void main(String[] args) {
HK hk = new HK();
System.out.println("HK is a good boy");
}
}
这样即使HK.java不在hk这个目录下,直接在HK.java所在的目录下进行编译,也不会有问题,因为此时只是对HK中的代码进行解析,并不会加载HK这个类,即使在类中创建了HK的对象,在编译的时候也只会简单的把HK替换成hk/HK。但是如果试图运行java HK,则会报错,找不到hk\HK,因为此时会在hk的目录下寻找HK.class。
java中的动态,如果一个类A引用了另一个类B,在编译之后,可以把B的.class随便替换,而不需要重新编译A,就可以直接运行。在《超越Java》中针对这种动态对Java进行了批判,说Java不够动态,Ruby会好很多。
一个简单的例子:
源文件开始的注释有文件所在的路径,所有源文件都在在test目录及其子目录下。
//:test/Test.java
import com.hk.*;
import com.hk.hking.HKing;
public class Test {
public static void main(String[] args) {
HK hk = new HK();
System.out.println("HK:");
hk.say();
HKing hking = new HKing();
System.out.println("HKing:");
hking.say();
}
}
//:test/package/com/hk/HK.java
package com.hk;
public class HK {
public void say() {
System.out.println("I am a good boy!");
}
}
//:test/package/com/hk/hking/HKing.java
package com.hk.hking;
public class HKing {
public void say() {
System.out.println("I am HK's nickname!");
}
}
如果直接在Test.java所在路径下用javac Test.java则会出错,此时必须指明cp路径
即:javac -cp . ; package Test.java //表示有2个cp路径用;隔开
其中当前目录(.)也可以省略,因为Test中没有引用当前目录下的类,如果有,当前路径是必须的。
首先,通过-cp指定的路径建立类路径引用表:
++++++++++++
+ . +
++++++++++++
+ package +
++++++++++++
然后通过import分别建立类引用表:
+++++++++++++++++++++
+ com.hk.hking.HKing +
+++++++++++++++++++++
相对类引用表:
+++++++++++++++
+ com.hk.* +
+++++++++++++++
这三个表都是有顺序的。
遇到HK,首先去当前目录和package目录下查找,没有;然后查看类引用表,没有HK这个类;然后到./com/hk/下查找,因为当前目录下没有com,所以找不到;然后到./package/com/hk/查找,找到HK.java,编译为HK.class.
遇到HKing,还是先到当前目录和package目录下查找,没有;然后查看类引用表,然后把HKing.java编译为HKing.class。
运行则需要用java -cp .;package Test,此时的(.)是必须的。
运行时.class文件中的类都已经成了全名,HK为com/hk/HK,,HKing为com/hk/hking/HKing,这种只是给出了相对路径,-cp指定的路径是指明去哪里搜索这些相对路径。
总结:
1、类文件一定要放在其所在的包指出的目录下
2、编译时直接到源文件所在目录编译即可,但是要指明-cp
3、运行编译后的文件也要指明-cp,如果运行的类是在包里,编译时应该指明包名。(这一点未作说明,如:java com.test.Test)