Java学习之旅第二季-9:包
9.1 包的概念与作用
在项目开发中,将程序中相关的部分组合在一起通常很有帮助。在 Java 中,这可以通过使用模块/包来实现。本小节,我只关注包的使用。从Java 9 开始 JDK 中的 API 组织方式发生了很大变化,已经开始按照模块组织类、接口等了。不过为了向下兼容,开发不使用模块的 Java 项目时,可以不用关注模块,直接使用包即可。
通常来说,当我们为一个类命名时,就是在从命名空间中分配一个名称。命名空间定义了一个声明区域。在 Java 中,来自同一个命名空间的两个类不能使用相同的名称。因此,在给定的命名空间内,每个类名都必须是唯一的、独特的。
在大型项目中,为每个类找到独特的名称可能会很困难。此外还必须避免与其他在同一项目中工作的程序员所创建的代码以及 Java 库中的代码发生名称冲突。这些问题的解决方案就是包,因为它为开发人员提供了一种对命名空间进行划分的方法。当一个类定义在包内时,该包的名称会附加到每个类上,从而避免与具有相同名称但位于其他包中的其他类发生名称冲突。
由于一个包通常会包含相关的类,所以 Java 为包内的代码定义了特殊的访问权限。在一个包中,可以定义某些代码,使其仅能被同一包内的其他代码访问,而不能被包外的代码访问。
总结起来,包的作用有以下三点:
- 相关功能的类可以以包的形式组织在一起,包内的类必须通过其包名来访问。因此,包提供了一种为类集合命名的方式
- 不同的包中允许同名的类,这样有效避免了命名冲突
- 包参与 Java 的访问控制机制。包内定义的类可以被设为仅在该包内可见,外部代码无法访问。因此,包提供了一种封装类的方法。
9.2 包的声明
在 Java 中,所有的类都属于某个包。如果没有指定包声明,就会使用默认包,默认包是没有名称的,默认的包适用于简短的示例程序,但对于实际应用来说则不够适用。通常情况下,我们会为代码定义一个或多个包。
要创建一个包,需在 Java 源文件的顶部添加一个 “package” 语句。该文件中声明的类将属于指定的包。
使用方式如下:
package com.laotan.article9; // 包的声明/*** @author 老谭*/
public class PkgDemo {
}
上述代码中有几个注意的语法点:
- 使用package关键字,在后面加上包名,该语句必须放在类文件的第一行(注释除外),且只能出现一次
- 文件存放的位置一定与包名对应,如果使用IDEA等开发工具创建类,只有不手动移动文件位置,则一般不会出现不对应的情况
在开发中,对于包名的命名,一般有如下规则:
- 不能以数字,特殊字符开头
- 不能使用 Java 关键字
- 所有的字母小写(约定俗成,不是语法要求的)
- 采用域名反写,因为域名是全世界第一无二的,如果反写也是一样不可重复的
Java 是通过文件系统来管理包的,每个包都会被存储在单独的目录中。例如,我声明的包是 “com.laotan.article”,则此包的任何类文件必须存储在项目源码根目录的 “com/laotan/article” 的子目录中,而编译之后的 class 文件就存储在类路径下同名的目录中。简而言之,目录必须与包名完全一致。
公用部分名称并不会使两个包有关联,如 com.laotan 与 com.laotan.article 就是没有关联的两个包
9.3 JDK内置包
在JDK本身提供的API中,也是按照模块/包来自组织存放的,不考虑模块,以下是常见的包及描述:
包名 | 描述 |
---|---|
java.lang | 核心类,无需显式导入 |
java.util | 工具类,集合框架等 |
java.util.concurrent | 并发编程相关的API |
java.io | 输入输出相关API |
java.nio | 非阻塞IO相关API |
java.net | 网络编程相关API |
java.sql | 操作关系数据库的JDBC |
java.time | 日期时间API |
java.math | 数学计算相关API |
javax.swing | GUI开发相关API |
9.4 类的导入
要使用不同包中的成员(以类为例),需要使用import关键字导入。
该语句出现在package语句后和类声明之前,且可以出现多次。
语法1:单类型导入
import pkg.ClassName;
语法2:通配符(Wildcard)导入
impotr pkg.*; //不允许出现多个*,且*只能在最后
语法3:静态导入
import static pkg.ClassName.staticMember; // 不能导入不同类中的同名静态成员
语法4:静态通配符导入
import static pkg.ClassName.*;
如果要使用的类是同一个包中或位于 java.lang 包中则不用显式导入,比如之前多次用到的 String 类。
9.5 类的导入顺序
正如之前提到的,包与目录是一一对应的。那么JVM如何知道去何处查找包呢?
首先,默认情况下,Java 运行时系统会将当前工作目录作为其起始点。因此,如果您的包位于当前目录的一个子目录中,那么它就会被找到。
其次,您可以通过设置 CLASSPATH 环境变量来指定一个或多个目录路径。
第三,您可以使用 java 和 javac 的 -classpath 选项来指定您的类的路径。需要指出的是,从 JDK 9 开始,一个包可以是模块的一部分,因此会在模块路径中被找到。不过,关于模块和模块路径的讨论将在第 15 章中进行。目前,我们将仅使用类路径。对于
- 如果使用的是完整的类名,如:com.laotan.article9.ImportDemo,那么直接根据完整类名查找该类接口
- 如果使用的是简单类名,如:ImportDemo,那么规则会复杂许多:
- 查找import语句是否导入了该类,如果导入了则使用
- 查找当前包是否存在该类,存在则直接使用
- 查找是否使用通配符导入了类所在的包,如果导入了则使用
- 查找 java.lang 中是否存在该类,如果存在则使用 java.lang 中的类
如果要使用不同包中同名的类,则可以使用全限定名使用该类
9.6 静态成员的导入顺序
- 如果使用类访问静态成员,如:MyClass.staticMember,直接根据类名查找其成员
- 如果直接使用静态成员,如: staticMember,则按照以下规则依次查找:
- 查找当前类是否声明了该静态成员,如果声明了直接使用
- 查找是否明确静态导入了其他类的该静态成员,如果导入了则使用
- 查找是否使用通配符静态导入了该成员所在的类,如果导入了则使用
9.7 小结
Java包机制是组织代码的重要方式,主要作用包括:组织相关类、避免命名冲突和实现访问控制。包通过目录结构管理,必须与包名严格对应。JDK API按功能组织在标准包中(如java.lang、java.util等)。使用import语句导入其他包的类,导入顺序遵循特定查找规则。静态成员导入使用import static语法。若遇到同名类冲突,需使用全限定名。包名通常采用反向域名命名法(如com.laotan.article)确保唯一性。