java 9+模块化后,默认访问权限语义变为“同包且同模块”,模块边界优先于包名,exports对默认成员无效,跨模块需opens或--add-opens支持反射。

Java 的包访问权限(即默认访问级别,不加 public、protected 或 private)在引入模块系统(Java 9+ 的 JPMS,Java Platform Module System)后,语义发生了实质性变化:它不再仅由“同一包”决定,而必须同时满足包在同一个模块内这一前提。
包访问权限的前提从“同包”变为“同包且同模块”
在 Java 8 及之前,只要两个类位于同一包(无论是否在同一个 JAR 或源路径中),彼此就能访问默认访问级别的成员。模块化之后,即使两个类物理上包名完全相同(如都叫 com.example.util),若分属不同模块,它们无法互相访问默认访问的类、方法或字段——JVM 会直接拒绝,编译期或运行期都会报错。
- 模块边界成为访问控制的第一道闸门;包可见性必须建立在模块导出(
exports)和开放(opens)的基础上 - 默认访问权限本质上变成了“模块内私有”,与
private类似,但作用域扩大到整个模块内的同包子集 - 这意味着:即使你用
--add-exports强行导出某个包,也无法让其他模块访问其默认访问成员——因为 JPMS 不允许跨模块绕过访问修饰符
模块声明直接影响包访问的实际效果
一个包要被其他模块使用,至少需通过 exports 显式声明;但 exports 只影响 public 成员的可访问性。对默认访问成员而言,exports 完全无效。
-
exports com.example.service;→ 允许其他模块访问该包中public类及其public成员 - 该包中
void doWork() { ... }(默认访问)仍仅限本模块内调用,哪怕已 exports - 若需跨模块调用非 public 成员(如反射),必须配合
opens(用于反射访问)或open module(不推荐)
常见误用与调试提示
升级到模块化项目后,原本能运行的代码可能突然抛出 IllegalAccessError 或编译失败,典型场景包括:
- 测试类(在
test源集)试图直接访问主代码中默认访问的工具类——若测试与主代码属于不同模块(如未用open或未正确设置--add-opens),就会失败 - 多个模块各自定义了
internal.util包,相互误以为“同包可访问”,实则完全隔离 - 使用 Spring 或 Mockito 等框架时,因代理/反射访问默认访问方法失败,需显式配置
--add-opens(如--add-opens my.module/com.example=ALL-UNNAMED)
迁移建议
面向模块化设计时,应主动管理访问边界:
- 将仅供模块内部使用的类/方法保持默认访问,这是安全且符合模块封装的设计
- 需要对外暴露的功能,务必声明为
public,并配合exports模块指令 - 避免“靠同包绕过访问控制”的隐式耦合;模块化鼓励显式契约(接口 + public API)
- 开发阶段可用
--illegal-access=warn(Java 16+ 默认为deny)提前发现非法反射访问
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











