4.7 包
Java允许使用包(package)将类组织起来.借助于包可以方面地组织自己的代码,并将自己的代码与别人提供的代码库分开管理.标准的Java类分布在多个包中,包括java.lang,java.util和java.net等.标准的java包具有一个层次结构.如同硬盘的目录嵌套一样,也可以使用嵌套层次组织包.所有标准的Java包都处于java和javax包层次中.
使用包的主要原因是确保类名的唯一性.假如两个程序员不约而同地建立了Employee类.只要将这些类放置在不同包中,就不会产生冲突.事实上,为了保证包名的绝对唯一性,Sun公司建议将公司的因特网域名(这显然是独一无二的)以逆序的形式作为包名,并且对于不同的项目使用不同的子包.例如,horstmann.com是本书作者之一注册的域名,逆序形式为com.horstmann.这个包还可以进一步划分成子包,例如com.horstmann.corejava.
从编译器的角度来看,嵌套的包之间没有任何关系.例如,java.util包与java.util.jar包毫无关系.每一个都拥有独立的类集合.
4.7.1 类的导入
一个类可以使用所属包中的所有类,以及其他包中的公有类.可以采用两种方式访问另一个包中的公有类. 第一种方式是在每一个类名之前添加完整的包名.例如:java.util.Date tody = new java.util.Date();
这显然令人讨厌,
第二种方法是使用 import 语句. import 语句是一种引用包含在包中的类的简明描述.一旦使用了 import 语句,在使用类时,就不必写出包的全名了.
可以使用 import 语句导入一个特定的类或者整个包. import 语句应该位于源文件的顶部(但位于 package 语句的后面).例如,可以使用下列语句导入java.util包中所有的类.
import java.util.*;
然后,就可以使用
Date today = new Date();
而无须在前面加上包前缀.还可以导入一个包中的特定类:
import java.util.Date;
java.util.*的语法比较简单,对代码的大小也没有任何负面影响.当然,如果能够明确地指出所导入的类,将会使代码的读者更准确地知道了加载了哪些类.
提示:在Eclipse中,可以使用菜单选项Source-Organize Import.Package语句,如 import java.util.*,将会自动地扩展指定的导入列表,如:
import java.util.ArrayList;
import java.util.Date;
这是一个十分便捷的特性.
但是,需要注意的是,只能使用星号(*)导入一个包,而不能使用 import java.*或 import java.*.*导入以java为前缀的所有包.
在大多数情况下,只导入所需的包,并不必过多地理睬它们.但 在发生命名冲突的时候,就不能不注意包的名字了.例如,java.util和java.sql包都有日期类.如果在程序中导入了这两个包:
import java.util.*;
import java.sql.*;
在程序使用Date类的时候,就会出现一个编译错误:
Date today; // ERROR--java.util.Date or java.sql.Date?
此时编译器无法确定程序使用的是哪一个Date类,可以采用增加一个特定的 import 语句来解决这个问题:
import java.util.*;
import java.sql.*;
import java.util.Date;
如果这两个Date都需要使用,又该怎么办呢?答案是,在每个类名的前面加上完整的包名.
java.util.Date deadline = new java.util.Date();
java.sql.Date today = new java.sql.Date(...);
在包中定位类是编译器的工作.类文件中的字节码肯定使用完整的包名来引用其他类.
注释:C++程序员经常将 import 和 #include 弄混.实际上,这两者之间没有共同之处.在C++中,必须使用#include将外部特性的声明加载进来,这是因为C++编译器无法查看任何文件的内部,除了正在编译的文件以及在头文件中明确包含的文件.Java编译器可以查看其它文件的内部,只要告诉它到哪里去查看就可以了.
在Java中,通过显式地给出包名,如java.util.Date,就可以不使用 import;而在C++中,无法避免使用#include .
import 语句的唯一好处是简捷.可以使用简短的名字而不是完整的包名来引用一个类.例如,在 import java.util.*(或 import java.util.Date)语句之后,可以仅仅用Date引用java.util.Date类.
在C++中,与包机制类似的是命名空间(namespace),在Java中,package 与 import 语句类似于C++中的 namespace 和 using 指令.
4.7.2 静态导入
import 语句不仅可以导入类,还添加了导入静态方法和静态域的功能.例如,如果在源文件的顶部,添加一条指令:
import static java.lang.System.*;
就可以使用System类的静态方法和静态域,而不必加类名的前缀.
out.println("Goodbye, world"); // 等价于 System.out
exit(0); // 等价于 System.exit
另外,还可以导入特定的方法或域:
import static java.lang.System.out;
实际上,是否有更多的程序员采用System.out和System.exit的简写形式,似乎是一件值得怀疑的事情,这种编写形式不利于代码的清晰度.不过:
sqrt(pow(x, 2) + pow(y, 2))
看起来比
Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
清晰得多.
4.7.3 将类放入包中
要想将一个类放入包中,就必须将包的名字放入源文件的开头,包中定义类的代码之前.例如,程序4-7的文件Employee.java开头是这样的:package com.horstmann.corejava;
public class Employee
{
...
}
如果没有在源文件中放置 package 语句,这个源文件中的类就放置在一个默认包(default package)中.默认包是一个没有名字的包.在此之前,我们定义的所有类都在默认包中.
将包中的文件放到完整的报名匹配的子目录中.例如,com.horstmann.corejava包中所有源文件都应该被放置在子目录com/horstmann/corejava中.编译器将类文件也放在相同的目录结构中.
程序4-6和程序4-7分别放在两个包中:PackageTest类放置在默认包中;Employee类放置在com.horstmann.corejava包中.因此,Employee.java文件必须包含在子目录com/horstmann/corejava中.
要想编译这个程序,只需改变基目录(当前目录),并运行命令
javac PackageTest.java
编译器就会自动地查找文件com/horstmann/corejava/Employee.java(在当前目录下,即PackageTest.java所在目录)并进行编译.下面看一个更加实际的例子.在这里不使用默认包,而是将类分别放在不同的包中(com.horstmann.corejava和com.mycompany).
在这种情况下,仍然要从基目录编译和运行类,即包含com目录:
javac com/mycompany/PayrollApp.java
java com.mycompany.PayrollApp
需要注意,编译器对文件(带有文件分隔符和扩展名.java的文件)进行操作.而Java解释器加载类(带有.分隔符).
提示:从下一章开始,将对源代码使用包,这样一来,就可以为各章建立一个IDE工程,而不是各小节分别建立工程.
警告:编译器在编译源文件的时候不检查目录结构.例如,假定有一个源文件开头有下列语句:
package com.mycompany;
即使这个源文件没有在子目录com/mycompany下,也可以进行编译.如果它不依赖其他包,就不会出现编译错误.但是,最终的程序将无法运行,因为虚拟机找不到类文件.
程序4-6 PackageTest.java如下所示:
import com.horstmann.corejava.*;
// the Employee class is defined in that package
import static java.lang.System.*;
public class PackageTest
{
public static void main(String[] args)
{
// because of the import statement, we don't have to use com.horstmann.corejava.Employee here
Employee harry = new Employee("Harry Hacker", 5000, 1999, 10, 1);
harry.raiseSalary(5);
// because of the static import statement, we don't have to use System.out here
out.println("name = " + harry.getName() + ", salary = " + harry.getSalary());
}
}
程序4-7 Employee.java如下所示:
package com.horstmann.corejava;
// the classes in this file are part of this package
import java.util.*;
// import statements come after the package statement
public class Employee
{
private String name;
private double salary;
private Date hireDay;
public Employee(String n, double s, int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
// GregorianCalendar uses 0 for January
hireDay = calendar.getTime();
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public Date getHireDay()
{
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
}
注意,PackageTest.java所在目录下不能有Employee的相关文件,否则可能遮掩包中的Employee,导致程序出错.