Java import 详解
1. package 机制
Java 的 package 机制类似于 C++ 的 namespace 机制。
在编写 Java 程序时,随着程序架构越来越大,类的个数也越来越多,这时就会发现管理程序中维护类名称也是一件很麻烦的事,尤其是一些同名问题的发生。有时,开发人员还可能需要将处理同一方面的问题的类放在同一个目录下,以便于管理。
为了解决上述问题,Java 引入了包(package)机制,提供了类的多层命名空间,用于解决类的命名冲突,类文件管理等问题。
包允许将类组合成较小的单元(类似文件夹),它基本上隐藏了类,并避免了名称上的冲突。包允许在更广泛的范围内保护类,数据和方法。你可以在包内定义类,而在包外的代码不能访问该类。这使你的类相互之间有隐私,但不被其他世界所知。
2. 包名与类名
package
名称就像是我们的姓,而 class
名称就像是我们的名字 。package
和package
的附属关系用 .
来连接,这就像是复姓。比如说 java.lang.String
就是复姓 java.lang
,名字为 String
的类别,java.io.InputStream
则是复姓 Java.io
名字为 InputStream
的类别。
在 Java 程序文件中需要引用外部的类时需要指定被引用类所在的包名,可以使用 import
将类所在的包导入到当前文件,导入后使用外部类则不需要指定完整的包名。
3. 两种导入声明
Java 支持两种导入声明,分别为 单类型导入(single-type-import) 和 按需类型导入(type-import-on-demand),注意只有声明为 public
类型的类和接口才能被导入。
单类型导入,这很好理解类似于 import java.io.File
这样,仅仅导入一个 public
类或者接口,我们在编写 Java 代码时一般习惯于这种方式。
按需类型导入,类似于 import java.io.*
这样,通过通配符 *
定义导入方式,在这里 *
号表示的意思是指定使用按需导入方式,不要误以为是导入了某个包下的所有类。这里需要注意了使用这种方式并不是直接将某个包下的所有类通通导入到当前文件中,而是视使用情况而定。换句话说就是将被使用到的类导入,未使用到的类不导入。
4. 按需导入分析
4.1 类目录定位
Java 编译器会从启动目录(bootstrap),扩展目录(extension)和用户类路径下去定位需要导入的类,但是这三种目录仅仅是需要被导入类的顶层目录。那么编译器是怎么得到类的位置的呢?答案是通过查找,知道顶层路径后通过查找顶层路径下可能的包,类,文件,最终可以得到若干条可能的绝对路径,绝对路径的样式大致如下:
绝对路径
= 顶层路径名
(即启动目录或扩展目录再或者用户类目录) / 包名
/ 文件名
.class
4.2 查找机制
对于单类型导入很简单,因为包明和文件名都已经确定,所以可以一次性查找定位。对于按需类型导入则比较复杂,编译器会把包名和文件名进行排列组合,然后根据所有的可能性进行查找。
package com;
import java.io.*;
import java.util.*;
当你的类文件中用到了 File
类,我们很容易就知道 File
类位于 java.io
中,但是对于编译器来说它并不知道,所以它需要搜索所有可能出现 File
类的地方,那么可能出现 File
类的位置或目录有哪些呢?
File
,如果 File 类属于无名包,就是说 File 类没有使用 package 语句声明,此时编译器会首先搜索无名包。
com.File
,如果 File 类处于当前包中。
java.lang.File
,编译器会自动导入的 java.lang 包,并搜索该包。
java.io.File
, Java 的核心输入输出流 java.io 包。
java.util.File
, Java 的实用工具类库。
以上就是可能出现 File
类的五个位置,需要注意的地方就是,编译器在找到 java.io.File 类之后并不会停止下一步的寻找,而是会把所有的可能性都查找完毕以确定是否有相同的类导入冲突。
假设此时的顶层路径有三个,那么编译器就会进行 3x5=15
次查找。
注意:如果在查找完成后,编译器发现了两个同名的类,那么就会产生报错。删除你不用的那个类,然后再次编译。
4.3 结论
了解以上原理之后,我们可以得出这样的结论:按需类型导入是绝对不会降低 Java 代码的执行效率的,但会影响到 Java 代码的编译速度。
查看 JDK
的源代码就知道 SUN
的软件工程师一般不会使用按需类型导入。因为使用单类型导入至少有以下两点好处:
-
提高编译速度。
-
避免命名冲突。
-
当然,使用单类型导入会使 import 语句较长。
5. 编译优化
不被使用的类的导入声明,最终都会被编译器优化去除,不会出现在 class 文件中。Java 中的 import
与 C 语言中的 include
不同(C 语言的 #include
会将被包含文件的内容复制并替换当前文件的 #include
语句),import
不会将导入声明的类写入到 class
文件中,而是各自依旧使用独立的 class
文件。