包由一些具名的类、接口和其他引用类型组成,目的是把相关的类组织在一起,并为这些类定义命名空间。
Java 平台的核心类放在一些名称以 java 开头的包中。例如,Java 语言最基本的类在 java.lang 包中,各种实用类在 java.util 包中,输入输出类在 java.io 包中,网络类在 java.net 包中。有些包还包含子包,例如 java.lang.reflect 和 java.util.regex。甲骨文标准化的 Java 平台扩展一般在名称以 javax 开头的包中。有些扩展,例如 javax.swing 及其各种子包,后来集成到了核心平台中。最后,Java 平台还包含几个被认可的标准,这些包以标准制定方命名,例如 org.w3c 和 org.omg。
每个类都有两个名称:一个是简称,定义时指定;另一个是完全限定名称,其中包含所在包的名称。例如,String 类是 java.lang 包的一部分,因此它的完全限定名称是 java.lang.String。
本文说明如何把自己的类和接口放到包里,以及如何选择包名,避免和其他人的包名有冲突。然后说明如何有选择性地把类型名称或静态成员导入命名空间,避免每次使用类或接口都要输入包名。
声明包
若想指定类属于哪个包,要使用 package 声明。如果 Java 文件中有 package 关键字,必须是 Java 代码的第一个标记(即除了注释和空格之外的第一个标记)。package 关键字后面是包的名称和一个分号。例如,有个 Java 文件以下述指令开头:
package org.apache.commons.net;
那么,这个文件中定义的所有类都是 org.apache.commons.net 包的一部分。
如果 Java 文件中没有 package 指令,那么这个文件中定义的所有类都是一个默认的无名包的一部分。此时,类的限定名称和不限定名称相同。
包的名称有可能冲突,所以不要使用默认包。项目在增长的过程中越来越复杂,冲突几乎是不可避免的,所以最好从一开始就创建包。
全局唯一的包名
包的重要功能之一是划分 Java 命名空间,避免类名有冲突。例如,只能从包名上区分java.util.List 和 java.awt.List 两个类。不过,因此包名本身就要独一无二。作为 Java的开发方,甲骨文控制着所有以 java、javax 和 sun 开头的包名。
常用的命名方式之一是使用自己的域名,倒序排列各部分,作为包名的前缀。例如,Apache 项目开发了一个网络库,是 Apache Commons 项目的一部分。Commons 项目的网址是 http://commons.apache.org/,因此这个网络库的包名是 org.apache.commons.net。
注意,API 开发者以前也使用这种包命名规则。如果其他程序员要把你开发的类和其他未知类放在一起使用,你的包名就要具有全局唯一性。如果你开发了一个 Java 程序,但是不会发布任何类供他人使用,那么你就知道部署这个应用需要使用的所有类,因此无需担心无法预料的命名冲突。此时,可以选择一种自己用着方便的命名方式,而不用考虑全局唯一性。常见的做法之一是,使用程序的名称作为主包的名称(主包里可能还有子包)。
导入类型
默认情况下,在 Java 代码中引用类或接口时,必须使用类型的完全限定名称,即包含包名。如果编写的代码需要使用 java.io 包中的 File 类处理文件,必须把这个类写成 java.io.File。不过这个规则有三个例外:
· java.lang 包中的类型很重要也很常用,因此始终可以使用简称引用;
· p.T 类型中的代码可以使用简称引用 p 包中定义的其他类型;
· 已经使用 import 声明导入命名空间里的类型,可以使用简称引用。
前两个例外叫作“自动导入”。java.lang 包和当前包中的类型已经导入到命名空间里了,因此可以不加包名。输入不在 java.lang 包或当前包中的常用类型的包名,很快就会变得冗长乏味,因此要能显式地把其他包中的类型导入命名空间。这种操作通过 import 声明实现。
import 声明必须放在 Java 文件的开头,如果有 package 声明的话,要紧随其后,并且在任何类型定义之前。一个文件中能使用的 import 声明数量不限。import 声明应用于文件中的所有类型定义(但不应用于 import 声明中的类型)。
import 声明有两种格式。若想把单个类型导入命名空间,import 关键字后面是类型的名称和一个分号:
import java.io.File; // 现在不用输入java.io.File了,输入File就行
这种格式叫“单个类型导入”声明。
import 声明的另一种格式是“按需类型导入”。在这种格式中,包名后面是 .* 字符,表示使用这个包里的任何类型时都不用输入包名。因此,如果除了 File 类之外,还要使用java.io 包中的其他几个类,可以导入整个包:
import java.io.*; // java.io包中的所有类都可以使用简称
按需导入句法对子包无效。如果导入了 java.util 包,仍然必须使用完全限定名称 java.util.zip.ZipInputStream 引用这个类。
按需导入类型和一个一个导入包中的所有类型作用不一样。按需导入更像是使用单个类型导入句法把代码中真正用到的各种类型从包中导入命名空间,因此才叫“按需”导入——用到某个类型时才会将其导入。
命名冲突和遮盖
import 声明对 Java 编程极其重要。不过,可能会导致命名冲突。例如,java.util 和java.awt 两个包中都有名为 List 的类型。
java.util.List 是常用的重要接口。java.awt 包中有很多客户端应用常用的重要类型,但java.awt.List 已经作废了,不是这些重要类型的其中一个。在同一个 Java 文件中既导入java.util.List 又导入 java.awt.List 是不合法的。下述单个类型导入声明会导致编译出错:
import java.util.List;
import java.awt.List;
使用按需类型导入句法导入这两个包是合法的:
import java.util.*; // 导入集合和其他实用类型
import java.awt.*; // 导入字体,颜色和图形类型
可是,如果试图使用 List 类型会遇到困难。这个类型可以从两个包中的任何一个“按需”导入,只要试图使用未限定的类型名引用 List 就会导致编译出错。这种问题的解决方法是,明确指定所需的包名。
因为 java.util.List 比 java.awt.List 常用得多,所以可以在两个按需类型导入声明后使用单个类型导入声明指明从哪个包中导入 List:
import java.util.*; // 导入集合和其他实用类型
import java.awt.*; // 导入字体,颜色和图形类型
import java.util.List; // 与java.awt.List区分开
这样,使用 List 时指的是 java.util.List 接口。如果确实需要使用 java.awt.List 类,只要加上包名就行。除此之外,java.util 和 java.awt 之间没有命名冲突了,在不指定包名的情况下使用这两个包中的其他类型时,会“按需”将其导入。
导入静态成员
除了类型之外,还可以使用关键字 import static 导入类型中的静态成员。和类型导入声明一样,静态成员导入声明也有两种格式:单个静态成员导入和按需静态成员导入。假如你在编写一个基于文本的程序,要向 System.out 输出大量内容,那么可以使用下述单个静态成员导入声明减少输入的代码量:
import static java.lang.System.out;
加入这个导入声明后,可以用 out.println() 代替 System.out.println()。又假如你编写的一个程序要使用 Math 类中的很多三角函数和其他函数。在这种明显要大量使用数字处理方法的程序中,重复输入类名“Math”不会让代码的思路更清晰,反而会起到反作用。遇到这种情况,或许应该按需导入静态成员:
import static java.lang.Math.*
加入这个导入声明后,可以编写 sqrt(abs(sin(x))) 这样简洁的表达式,而不用在每个静态方法前都加上类名 Math。
import static 声明另一个重要的作用是把常量导入代码,尤其适合导入枚举类型。假如你想在自己编写的代码中使用下述枚举类型中的值:
package climate.temperate;
enum Seasons { WINTER, SPRING, SUMMER, AUTUMN };
那么,可以导入 climate.temperate.Seasons,然后在常量前加上类型名,例如 Seasons.SPRING。如果想编写更简洁的代码,可以导入这个枚举类型中的值:
import static climate.temperate.Seasons.*;
使用静态成员导入声明导入常量一般来说比实现定义常量的接口更好。
静态成员导入和重载的方法
静态成员导入声明导入的是“名称”,而不是以这个名称命名的某个具体成员。因为 Java允许重载方法,也允许类型中的字段和方法同名,所以单个静态成员导入声明可能会导入多个成员。例如下述代码:
import static java.util.Arrays.sort;
这个声明把名称“sort”导入命名空间,而没有导入 java.util.Arrays 里定义的 19 个sort() 方法中的任何一个。如果使用导入的名称 sort 调用方法,编译器会根据方法的参数类型决定调用哪个方法。
从两个或多个不同的类型中导入同名的静态方法也是合法的,只要方法的签名不同就行。下面举个例子:
import static java.util.Arrays.sort;
import static java.util.Collections.sort;
你可能觉得上述代码会导致句法错误,其实不然,因为 Collections 类中定义的 sort() 方法和 Arrays 类中定义的所有 sort() 方法签名都不一样。在代码中使用“sort”这个名称时,编译器会根据参数的类型决定使用这 21 个方法中的哪一个。