背景与动机
当项目越来越大时,仅靠 package 已经不一定够用了。我们还会关心:
- 这个子系统依赖谁
- 哪些包允许暴露给外部
- 哪些内部实现不应该被随便访问
Java 9 引入的模块系统(JPMS,Java Platform Module System)就是为了解决这类问题。
核心原理拆解
1. 模块比包更高一层
可以先这样理解:
package:组织类module:组织包和依赖关系
也就是说,包解决“类怎么分目录”,模块解决“这一组包整体怎么对外暴露、怎么依赖别的模块”。
2. 模块的入口文件是 module-info.java
一个最简单的模块声明长这样:
module com.example.app {}这个文件通常放在模块源码根目录下,用来声明模块名和模块规则。
3. requires:声明依赖哪些模块
module com.example.app { requires java.sql;}这表示当前模块依赖 java.sql 模块。
可以把它理解成:模块级别的“我需要用谁”。
4. exports:声明哪些包允许被外部访问
module com.example.app { exports com.example.service;}这表示外部模块可以访问 com.example.service 包中的公开类型。
如果某个包没有被 exports,即使里面有 public 类,其他模块也不一定能直接访问。
5. 模块边界和访问修饰符不是一回事
这里很容易和上一节《包、作用域与访问控制》混在一起。
public、private、protected、默认权限:解决类或成员在代码层面的可见性exports:解决整个包是否对其他模块开放
所以一个类即使是 public,如果它所在的包没有被模块 exports,别的模块仍然不一定能直接使用它。
6. package 和 module 的区别
这两个很容易混:
package是代码组织单位module是部署和依赖组织单位
简单说:
- 类属于包
- 包属于模块
7. 模块系统最核心的价值
它主要带来三类好处:
- 依赖关系更明确
- 对外暴露面更可控
- 大型项目边界更清晰
不过对初学者来说,不必把模块系统当成日常写小程序的必备知识。它在大型项目、框架、平台化工程里更有价值。
最小可运行结构示例
src├─ module-info.java└─ com └─ example └─ service └─ UserService.javamodule-info.java:
module com.example.app { exports com.example.service;}UserService.java:
package com.example.service;
public class UserService { public void hello() { System.out.println("hello"); }}这个示例展示了最基本的模块结构:模块名、包名、导出包。
常见陷阱与错误示例
1. 误以为 public 类天然对所有模块都可见
在模块系统里,public 还不够;如果所在包没有被 exports,外部模块依然可能访问不到。
2. 把模块和包当成同一个概念
包是类的分组方式,模块是包和依赖关系的分组方式,层级不同。
3. 小项目一开始就过度模块化
模块系统很有价值,但并不是所有小型练习项目都需要马上引入。先理解概念,再按实际规模决定是否使用更稳妥。
面试高频问题
1. 什么是 JPMS
JPMS 是 Java Platform Module System,也就是 Java 的模块系统,用来在包之上进一步管理依赖和暴露边界。
2. requires 和 exports 分别做什么
requires 用来声明依赖哪些模块,exports 用来声明哪些包对外可见。
3. 模块和包有什么区别
包组织类,模块组织包和依赖。包是代码层面的分组,模块是更高层级的边界控制单位。
4. public 类是不是天然能被别的模块访问
不一定。除了类本身是 public,它所在的包通常还需要被 exports 出去。
一句话总结
模块系统不是用来替代包的,而是在包之上再加一层“依赖声明和暴露控制”,更适合中大型 Java 工程。