Java 包(Packages)

更新于 2025-12-25

Jakob Jenkov 2018-08-18

Java 包是一种将彼此相关的 Java 类 分组到同一个“组”(即包)中的机制。当一个 Java 项目(例如应用程序或 API)变得越来越大时,将代码拆分为多个 Java 类,并进一步将这些类组织到多个 Java 包中,是非常有帮助的。通过将类划分到不同的包中,可以更容易地找到你正在寻找的特定类。

Java 包类似于文件系统中的目录。实际上,在磁盘上,一个包就是一个目录。属于同一个包的所有 Java 源文件和类文件都位于同一个目录中。

Java 包可以包含子包。因此,Java 包可以构成所谓的包结构。Java 包结构就像目录结构一样,是一个由包、子包以及其中包含的类组成的树形结构。在硬盘上,Java 包结构确实是以目录形式组织的;在 JAR 文件(一种 ZIP 格式的压缩文件)中,也是以目录形式存在。

下面是一个 Java 包结构的示例截图:

An example Java package structure.

示例 Java 包结构

图中最上方是一个名为 src 的目录,这是源代码根目录。它本身不是一个 Java 包。该目录下的所有子目录都对应于 Java 包。因此,collectionscomconcurrency 等都是 Java 包(在磁盘上就是目录)。在上图中,Java 包用文件夹图标表示。

我展开了两个子级 Java 包,以便你能看到其中的类。类在截图中用带字母 C 的蓝色小圆圈表示。

子包的完整路径是其名称加上所有祖先包名,用点号(.)分隔。例如,navigation 子包的完整路径为:

com.jenkov.navigation

同样,Java 类的全限定名(fully qualified name)也包括其包名。例如,Page 类的全限定名为:

com.jenkov.navigation.Page

创建 Java 包结构

要创建一个 Java 包,首先需要在硬盘上创建一个源代码根目录。这个根目录本身不属于包结构的一部分,它包含所有需要放入包结构中的 Java 源代码。

创建好源代码根目录后,就可以开始向其中添加子目录了。每个子目录对应一个 Java 包。你可以在子目录内再创建子目录,从而构建更深层次的包结构。


将类添加到包中

要将 Java 类添加到包中,必须完成以下两件事:

  1. 将 Java 源文件放在与目标 Java 包名称相匹配的目录中。
  2. 在类内部声明它所属的包。

将 Java 源文件放入与包结构匹配的目录结构中非常简单:只需创建一个源代码根目录,然后在其下递归地为每个包和子包创建目录。将类文件放入与目标包对应的目录中即可。

当你把 Java 源文件放入正确的目录(即匹配该类应归属的包)后,还必须在该源文件内部声明它属于哪个 Java 包。如下所示:

package com.jenkov.navigation;

public class Page {
    // ...
}

上面代码的第一行(加粗部分)声明了 Page 类属于 com.jenkov.navigation 包。


Java 包命名规范

Java 包名始终使用小写字母书写,这与 Java 类名不同(类名通常首字母大写)。

为了避免与其他公开的 Java 包重名,建议以公司域名的反写形式作为包层次结构的开头。例如,我的公司域名为 jenkov.com,那么我就应该从 com.jenkov 开始构建包结构。换句话说,顶层包名为 com,其下有一个名为 jenkov 的子包。


从其他 Java 包导入类

如果类 A 需要使用类 B,就必须在类 A 中引用类 B。如果类 A 和 B 位于同一个 Java 包中,Java 编译器允许它们直接相互引用。例如:

public class B {
    public void doIt() {
        // 做一些事情...
    }
}

public class A {
    public static void main(String[] args) {
        B theBObj = new B();
        theBObj.doIt();
    }
}

如果 A 和 B 在同一个包中,上述代码没有问题。但如果 A 和 B 位于不同的包中,那么类 A 必须导入(import)类 B 才能使用它。例如:

import anotherpackage.B;

public class A {
    public static void main(String[] args) {
        B theBObj = new B();
        theBObj.doIt();
    }
}

上面第一行就是导入类 B 的语句。此例假设类 B 位于名为 anotherpackage 的包中。

如果类 B 位于 anotherpackage 的子包中(例如 anotherpackage.util),则导入语句应写成:

import anotherpackage.util.B;

从一个包导入所有类

如果你需要使用某个包中的大量类,逐个导入会导致很多 import 语句。此时可以使用 * 通配符一次性导入整个包中的所有类。例如:

import anotherpackage.util.*;

使用类的全限定名

也可以不使用 import 语句,而是直接使用类的全限定名来引用另一个包中的类。全限定名包括从顶层包一直到包含该类的子包的完整路径,再加上类名本身。例如:

anotherpackage.util.TimeUtil;

你可以在代码中这样使用:

public class A {
    public static void main(String[] args) {
        anotherpackage.util.TimeUtil timeUtil = new anotherpackage.util.TimeUtil();
        timeUtil.startTimer();
    }
}

如何将类划分到包中

你可能会疑惑:应该如何决定创建哪些 Java 包?又该如何将类分配到这些包中?虽然没有官方标准,但有两种常用方法。

按层划分(Divide by Layer)

第一种方法是根据类所属的应用层级进行划分。例如,如果你的应用有数据库访问层,就可以创建一个 database 包,所有与数据库通信相关的类都放在这个包中。

按应用功能划分(Divide by Application Functionality)

第二种方法是根据类所支持的应用功能模块进行划分。例如,如果你的应用有一个用于计算养老金的功能模块,就可以创建一个名为 pension 的包,所有参与养老金计算的类(无论多少)都放入该包(或其子包)中。

结合我的域名,这个 pension 包的完整结构将是:

com.jenkov.pension

总共三层包:com(顶层)、jenkov(嵌套在 com 中)、pension(嵌套在 jenkov 中)。

随着应用规模扩大、类数量增加,按功能划分通常比按层划分效果更好。按层划分会导致固定的几个包不断膨胀;而按功能划分则会产生越来越多的功能包,但每个包内的类数量相对较少。我个人以及大多数同事都采用“按功能划分”的方式。


内置的 Java 包

Java 平台自带大量内置的 Java 包。这些包提供了程序员经常需要的各种功能,例如:

  • 读写本地硬盘文件
  • 通过网络或互联网发送和接收数据
  • 连接数据库
  • ……以及其他众多功能