包
了解包
为了使类型更容易查找和使用,避免命名冲突并控制访问权限,程序员将相关类型的组捆绑到包中。
定义:包是相关类型的分组,提供访问保护和命名空间管理。请注意,类型是指类、接口、枚举和注释类型。枚举和注释类型分别是类的特殊类型和接口,因此本节中通常将类型简称为类和接口。
Java 平台的一部分类型是各种包的成员,这些包按功能捆绑类:基本类位于 java.lang
中,用于读写(输入和输出)的类位于 java.io
中,等等。您也可以将自己的类型放入包中。
假设您编写了一组表示图形对象的类,例如圆形、矩形、直线和点。您还编写了一个接口 Draggable
,如果类可以被鼠标拖动,则类将实现该接口。
//in the Draggable.java file
public interface Draggable {
...
}
//in the Graphic.java file
public abstract class Graphic {
...
}
//in the Circle.java file
public class Circle extends Graphic
implements Draggable {
. . .
}
//in the Rectangle.java file
public class Rectangle extends Graphic
implements Draggable {
. . .
}
//in the Point.java file
public class Point extends Graphic
implements Draggable {
. . .
}
//in the Line.java file
public class Line extends Graphic
implements Draggable {
. . .
}
您应该将这些类和接口捆绑到一个包中,原因有很多,包括以下原因
- 您和其他程序员可以轻松地确定这些类型是相关的。
- 您和其他程序员知道在哪里可以找到可以提供与图形相关的功能的类型。
- 您的类型的名称不会与其他包中的类型名称冲突,因为包会创建一个新的命名空间。
- 您可以允许包内的类型彼此无限制地访问,但仍然限制包外类型的访问权限。
创建包
要创建包,您需要为包选择一个名称(命名约定将在下一节中讨论),并将带有该名称的包语句放在包含您要包含在包中的类型(类、接口、枚举和注释类型)的每个源文件的顶部。
包语句(例如,package graphics;
)必须是源文件中的第一行。每个源文件中只能有一个包语句,它适用于文件中的所有类型。
注意:如果您将多个类型放在单个源文件中,则只能有一个是公共的,并且它必须与源文件同名。例如,您可以在
Circle.java
文件中定义public class Circle
,在Draggable.java
文件中定义public interface Draggable
,在Day.java
文件中定义public enum Day
,等等。您可以在与公共类型相同的文件中包含非公共类型(强烈建议不要这样做,除非非公共类型很小并且与公共类型密切相关),但只有公共类型才能从包外部访问。所有顶层非公共类型都将是包私有的。
如果您将上一节中列出的图形接口和类放在名为 graphics
的包中,您将需要六个源文件,如下所示
//in the Draggable.java file
package graphics;
public interface Draggable {
. . .
}
//in the Graphic.java file
package graphics;
public abstract class Graphic {
. . .
}
//in the Circle.java file
package graphics;
public class Circle extends Graphic
implements Draggable {
. . .
}
//in the Rectangle.java file
package graphics;
public class Rectangle extends Graphic
implements Draggable {
. . .
}
//in the Point.java file
package graphics;
public class Point extends Graphic
implements Draggable {
. . .
}
//in the Line.java file
package graphics;
public class Line extends Graphic
implements Draggable {
. . .
}
如果您不使用 package
语句,您的类型最终将出现在未命名的包中。一般来说,未命名的包只适用于小型或临时应用程序,或者当您刚开始开发过程时。否则,类和接口应该属于命名包。
命名包和命名约定
随着世界各地的程序员使用 Java 编程语言编写类和接口,许多程序员很可能会对不同的类型使用相同的名称。事实上,前面的示例就是这样做的:它定义了一个 Rectangle
类,而 java.awt
包中已经存在一个 Rectangle
类。尽管如此,如果它们位于不同的包中,编译器仍然允许这两个类具有相同的名称。每个 Rectangle
类的完全限定名都包含包名。也就是说,graphics
包中 Rectangle
类的完全限定名是 graphics.Rectangle
,而 java.awt
包中 Rectangle
类的完全限定名是 java.awt.Rectangle
。
这很好用,除非两个独立的程序员对他们的包使用相同的名称。什么可以防止这个问题?约定。
包名全部小写,以避免与类或接口的名称冲突。
公司使用其反转的互联网域名作为其包名的开头,例如 com.example.mypackage
,用于由 example.com
的程序员创建的名为 mypackage
的包。
在单个公司内发生的名称冲突需要通过该公司的约定来处理,例如,在公司名称之后包含区域或项目名称(例如,com.example.region.mypackage
)。
Java 语言本身的包以 java.
或 javax.
开头。
在某些情况下,互联网域名可能不是有效的包名。如果域名包含连字符或其他特殊字符,如果包名以数字或其他字符开头,而这些字符在 Java 名称开头使用是非法的,或者如果包名包含保留的 Java 关键字(例如 int
),则会出现这种情况。在这种情况下,建议的约定是添加下划线。例如
域名 | 包名前缀 |
---|---|
hyphenated-name.example.org |
org.example.hyphenated_name |
example.int |
int_.example |
123name.example.com |
com.example._123name |
使用包成员
构成包的类型称为包成员。
要从包外部使用 public
包成员,您必须执行以下操作之一
- 通过其完全限定名引用成员
- 导入包成员
- 导入成员的整个包
每个都适用于不同的情况,如下面的部分所述。
通过其限定名引用包成员
到目前为止,本教程中的大多数示例都通过其简单名称引用类型,例如 Rectangle
和 StackOfInts
。如果您正在编写的代码与该成员位于同一个包中,或者该成员已被导入,则可以使用包成员的简单名称。
但是,如果您尝试使用来自不同包的成员,并且该包尚未导入,则必须使用成员的完全限定名,其中包括包名。以下是前面示例中在 graphics 包中声明的 Rectangle
类的完全限定名。
graphics.Rectangle
您可以使用此限定名来创建 graphics.Rectangle
的实例
graphics.Rectangle myRect = new graphics.Rectangle();
限定名对于不经常使用是可以的。但是,当重复使用名称时,重复键入名称会变得很繁琐,代码的可读性也会降低。作为替代方案,您可以导入成员或其包,然后使用其简单名称。
导入包成员
要将特定成员导入当前文件,请在文件开头、任何类型定义之前,但在 package
语句之后(如果有)放置一个 import
语句。以下是如何从上一节中创建的 graphics 包中导入 Rectangle
类。
import graphics.Rectangle;
现在您可以通过其简单名称引用 Rectangle
类。
Rectangle myRectangle = new Rectangle();
如果只使用 graphics 包中的几个成员,这种方法很有效。但是,如果您使用包中的许多类型,您应该导入整个包。
导入整个包
要导入特定包中包含的所有类型,请使用带有星号 (*
) 通配符的 import 语句。
import graphics.*;
现在您可以通过其简单名称引用 graphics 包中的任何类或接口。
Circle myCircle = new Circle();
Rectangle myRectangle = new Rectangle();
import 语句中的星号只能用于指定包中的所有类,如这里所示。它不能用于匹配包中类的子集。例如,以下内容不匹配 graphics 包中以 A
开头的所有类。
// does not work
import graphics.A*;
相反,它会生成编译器错误。使用 import 语句,您通常只导入单个包成员或整个包。
注意:另一种不太常见的导入形式允许您导入封闭类的公共嵌套类。例如,如果
graphics.Rectangle
类包含有用的嵌套类,例如Rectangle.DoubleWide
和Rectangle.Square
,您可以通过使用以下两个语句来导入Rectangle
及其嵌套类。
import graphics.Rectangle;
import graphics.Rectangle.*;
请注意,第二个 import 语句不会导入
Rectangle
。另一种不太常见的导入形式,即静态导入语句,将在本节末尾讨论。
为了方便起见,Java 编译器会为每个源文件自动导入两个完整的包
java.lang
包和- 当前包(当前文件的包)。
包的表观层次结构
乍一看,包似乎是分层的,但实际上并非如此。例如,Java API 包含一个 java.awt
包,一个 java.awt.color
包,一个 java.awt.font
包,以及许多其他以 java.awt
开头的包。但是,java.awt.color
包、java.awt.font
包以及其他 java.awt.xxxx
包都不包含在 java.awt
包中。前缀 java.awt
(Java 抽象窗口工具包)用于许多相关的包,以使关系显而易见,但不是为了显示包含关系。
导入 java.awt.*
会导入 java.awt
包中的所有类型,但不会导入 java.awt.color
、java.awt.font
或任何其他 java.awt.xxxx
包。如果您计划使用 java.awt.color
中的类和其他类型以及 java.awt
中的类型,则必须导入这两个包及其所有文件。
import java.awt.*;
import java.awt.color.*;
名称歧义
如果一个包中的成员与另一个包中的成员同名,并且两个包都被导入,则必须使用每个成员的限定名来引用它们。例如,graphics 包定义了一个名为 Rectangle
的类。 java.awt
包也包含一个 Rectangle
类。如果 graphics
和 java.awt
都被导入,则以下代码将产生歧义。
Rectangle rect;
在这种情况下,您必须使用成员的完全限定名来明确指定您想要使用哪个 Rectangle
类。例如,
graphics.Rectangle rect;
静态导入语句
在某些情况下,您需要频繁访问一个或两个类中的静态 final 字段(常量)和静态方法。反复使用这些类的名称会导致代码混乱。静态导入语句提供了一种方法来导入您想要使用的常量和静态方法,这样您就不需要再使用它们的类名作为前缀。
java.lang.Math
类定义了 PI
常量和许多静态方法,包括用于计算正弦、余弦、正切、平方根、最大值、最小值、指数等的方法。例如,
public static final double PI = 3.141592653589793;
public static double cos(double a) {
...
}
通常,要从另一个类中使用这些对象,您需要在前面加上类名,如下所示。
double r = Math.cos(Math.PI * theta);
您可以使用 static import
语句导入 java.lang.Math 的静态成员,这样您就不需要再使用类名 Math 作为前缀。Math 的静态成员可以单独导入
import static java.lang.Math.PI;
或作为一组导入
import static java.lang.Math.*;
导入后,静态成员可以使用,无需限定。例如,前面的代码片段将变为
double r = Math.cos(PI * theta);
显然,您可以编写自己的类,这些类包含您经常使用的常量和静态方法,然后使用静态导入语句。例如,
import static mypackage.MyConstants.*;
注意:谨慎使用静态导入。过度使用静态导入会导致代码难以阅读和维护,因为代码阅读者将无法知道哪个类定义了特定的静态对象。如果使用得当,静态导入可以通过消除类名重复来使代码更易读。
总结包
要为类型创建包,请将 package
语句作为包含类型(类、接口、枚举或注释类型)的源文件中的第一个语句。
要使用不同包中的公共类型,您有三种选择
- 使用类型的完全限定名,
- 导入类型,或
- 导入类型所属的整个包。
包的源文件和类文件的路径名反映了包的名称。
您可能需要设置 CLASSPATH
,以便编译器和 JVM 能够找到类型的 .class
文件。
上次更新: 2021 年 9 月 14 日
返回教程列表