Java 允许使用包(package)将类组织起来。借助于包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。
使用包的主要原因是确保类名的唯一性。假如两个程序员不约而同地建立了Employee类。只要将这些类放置在不同的包中,就不会产生冲突。
java.lang 包被默认导入。

类的导入

一个类可以使用所属包中的所有类,以及其他包中的公有类(public class)。有两种方法可以访问另一个包中的公有类。第一种方式是在每个类名之前添加完整的包名。例如:

  1. java.time.LocalDate today = java.time.LocalDate.now();

另外一种方式是使用 import,可以使用 import 语句导入一个特定的类或者整个包。import 语句应该位于源文件的顶部(但位于 package 语句的后面):

  1. import java.time.*;
  2. // or import java.time.LocalDate;
  3. LocalDate today = LocalDate.now();

需要注意的是,只能使用星号()导入一个包,而不能使用 import java. 或 import java.. 导入以 java 为前缀的所有包。
如果导入的包中发生命名冲突,例如 java.util 和 java.sql 包中都有 Date 类,并且你要使用这两个 Date 类,那就只能在类的前面加上完整的包名:

  1. java.util.Date dedaline = new java.util.Date();
  2. java.sql.Date today = new java.sql.Date(...);

静态导入

import 语句不仅可以导入类,还增加了导入静态方法和静态域的功能。
例如,添加下列代码:

  1. import static java.lang.System.*;
  2. // or import static java.lang.System.out;

就可以使用 System 类的静态方法和静态域,而不必加类名前缀:

  1. out.println("Goodobye, World!") // i.e., System.out
  2. exit(0); // i.e., System.exit

将类放入包中

要想将一个类放入包中,就必须将包的名字放在源文件的开头,包中定义类的代码之前。如果没有在源文件中放置 package 语句,这个源文件中的类就被放置在一个默认包(defaulf package)中。默认包是一个没有名字的包:

  1. package com.yikang.corejava;

将包中的文件放到与完整的包名匹配的子目录中。例如,com.yikang.corejava 包中的所有源文件应该被放置在子目录 com/yikang/corejava(Windows中com\yikang\corejava)中。编译器将类文件也放在相同的目录结构中。
比如,Main 类放置在默认包中,Employee 类放置在 com.yikang.corejava 包中。因此 Employee.java 文件必须包含在 com/yikang/corejava 中,换句话说,目录结构如下:

  1. .(base directory)
  2. ├── Main.java
  3. └── com
  4. └── yikang
  5. └── corejava
  6. └── Employee.java

要想编译这个程序,首先保证当前目录在基目录,然后运行:

  1. $ javac Main.java

编译器就会自动地查找文件 com/yikang/corejava/Employee.java 并进行编译。
再来举一个更实际的例子,不使用默认包,而是将类分别放在不同的包中(com.yikang.corejava 和 com.mycompany)

  1. .
  2. └── com
  3. ├── mycompany
  4. └── PayrollApp.java
  5. └── yikang
  6. └── corejava
  7. └── Employee.java

在这种情况下,仍然要从基目录编译和运行类,即包含com目录:

  1. $ javac com/mycompany/PayrollApp.java
  2. $ java com.mycompany.PayrollApp

编译器对文件(带有文件分隔符和拓展名 .java 的文件)进行操作,而 Java 解释器加载类(带有 . 分隔符)

编译器在编译源文件的时候不检查目录结构,例如,假定有一个源文件开头有下列语句:

  1. package com.mycompany;

即使这个源文件没有在子目录 com/mycompany 下,也可以进行编译。如果它不依赖于其他包,就不会出现编译错误。但是,最终的程序将无法运行,除非先将所有类文件移到正确的位置上。如果包与目录不匹配,虚拟机就找不到类。

包作用域

对于访问修饰符 public 和 private。

  • 标记为 public 的部分可以被任意的类使用;
  • 标记为 private 的部分只能被定义它们的类使用。
  • 如果没有指定 public 或 private,这个部分(类、方法或变量)可以被同一个包中的所有方法访问

java.awt 包是一个包,里面所有的文件都是属于这个包的。也就是说使用语句:

  1. package java.awt;

这个文件就是属于 java.awt 包。当然对于 Java 库文件,从 1.2 开始就不能定义以 java. 开始的包。
对于没有指定访问修饰符的类,可以被同一个包的所有方法访问。这可能会有安全问题。比如,在用户自定义的包中(Java 库包受保护)存在没有指定访问修饰符的域,我就可以创建相同的包文件,以达到修改用户自定义包中未指定访问修饰符的域。然而,可以通过包密封(package sealing)机制来解决将各种包混杂在一起的问题。