import在java中是非常普遍的关键字,只要是会java基础的人肯定会用,但如果不了解他的其原理,很容易会进入理解的误区。
基本概念
对于每个类,都有一个全名,其中包括两部分:所在的包名和 类名。例如JDK的Map类,全名:java.util.Map,包名:java.util,类名:Map。
JVM要识别这个类,就是通过全名来查找的,我们来看下面的字节码片段就能明白。
SourceCode:
Map map = new HashMap(16);
ByteCode:
0 new java.util.HashMap
3 dup
4 bipush 16 (int)
6 invokespecial
57: Ljava/util/Map;
其实,我们写Java代码的时候,准确的写法应该是带上类的全名:
java.util.Map map = new java.util.HashMap(16);
但是这样太累赘了,而且如果多处使用HashMap,还要到处带着这个长长的全名。所以我们通过import来导入我们需要的包,这样我们就只需要直接使用类名就可以了。就像我们的人名一样,我们平时一般直接称呼名字,更加方便,而且也会亲切不少呢。
所以import的作用相当于C++的命名空间namespace,指定好了用什么“姓”,JVM就知道怎么通过“名”来定为每一个类的“姓名”(全名)了,省了让我们每次都写类的全名,效率还是提高了不少。同时,也解决了不同包的类命名冲突问题。
误区-导入
这里有一个误区,我们平时老说“通过import来导入当前包的外部类”,这里的“导入”很容易让人产生误解,觉得import一个类就会把这个类include进来,或者说加载进来。
首先要澄清一下,import只是作为一个类的全名补充机制,在编译的时候,编译器会根据import的包来把类名补充成全名,转化为字节码。到此import的职责已经完成,字节码上面就看不到import的影子了,所以根本起不到include引入代码的作用,更别说后面JVM加载执行.class文件了。
我们可以举个例子:多import一个java.util.Set,看看是否对最终的字节码产生影响。
SourceCode:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Test {
public static void main(String[] args) {
Map map = new HashMap();
}
}
ByteCode:
1: Class: jscai.Test
8: Methodref: java.lang.Object. (()V)
9: NameAndType: (()V)
14: Utf8: main
15: Utf8: ([Ljava/lang/String;)V
16: Class: java.util.HashMap
18: Methodref: java.util.HashMap. (()V)
19: Utf8: args
20: Utf8: [Ljava/lang/String;
21: Utf8: map
22: Utf8: Ljava/util/Map;
23: Utf8: SourceFile
24: Utf8: Test.java
可以看出来,字节码中完全没有java.util.Set的影子,说明import只是作为一个编译器生成字节码的命名过度工具,如果代码中没有使用到该类,即使import也不会对字节码产生影响,所以对后面JVM的执行也是没有影响的。
同时,一些java的编码规范(如Sun、CheckStyle)都要求删除java文件中多余的import语句(见UnusedImports规则),又是为什么呢?
原来,编译器根据import拼装类全名的时候,会去遍历import导入的包,所以多余的import会影响编译的效率。同时,多余的import也会提高类名冲突发生的概率(引入的姓越多,就越容易撞名),而且还违背了clean code的原则。
所以,还是锦上添花,把多余的import干掉吧。而且在eclipse中,只要轻轻一按[ctrl+shift+O],就能把多余的import清空,以及补全缺少的import。
同理,在import的时候,最好使用“单类型导入(single-type-import)”,直接import你需要的类,而不是导入整个包,这样也能减少编译器多余的查找,节省性能。