QUICKREF · MODULES · JEP 511
Module Import Declarations:一行导入一个模块
JDK 25 · 速查主题 04 / 06 · 预计 25 分钟
一句话
import module java.base; 把 java.base 导出的所有包一次性导入,等价于几十条按需包导入。它解决的是 import 噪音,不是模块化:不改变 requires,不打开封装,也不能导入 classpath 上的未命名模块。学完你要能自己回答
- import module M 到底导入了哪些包?transitive requires 算不算?
- 两个模块有同名类时怎么办?单类型导入与按需导入谁遮蔽谁?
- 为什么 import module 不能替代模块系统?在命名模块里它还需要什么前提?
问题:java.base 有几十个包01
一个普通程序经常同时用到 java.util、java.io、java.time、java.util.concurrent 等包。传统写法是一串 import xxx.*;;模块导入把「一个模块导出的所有包」压成一行。
import module java.base;
import java.util.List;
public class ModuleImportDemo {
public static void main(String[] args) {
List<String> names = List.of("scoped", "values");
System.out.println(names);
System.out.println(java.time.LocalDate.of(2025, 9, 16));
}
}
javac --release 25 -d out ModuleImportDemo.java
java -cp out ModuleImportDemo
[scoped, values] 2025-09-16
import module可以出现在 classpath 代码里,不要求你自己写 module-info。- 它可以和普通 import 混用;同一个模块重复导入不报错。
- 它导入的是包,不是模块依赖;不会把模块加到运行时图里。
JEP 511 举的例子是:
import module java.base 等价于 54 条按需包导入。数量会随 JDK 版本变化,不要把它当固定契约。语义:导出的包 + 传递读到的模块02
import module M; 导入两类包:M 向当前模块导出的包,以及当前模块因为读取 M 而读取到的模块所导出的包(即 M 的 transitive requires 链)。这让「模块 API 引用了别的模块类型」时不必再逐条导入。
import module java.sql;
public class SqlImport {
DataSource ds; // javax.sql.DataSource,通过 java.sql 的传递读入
}
| 声明 | 典型导入范围 | 说明 |
|---|---|---|
import module java.base; | java.lang / java.util / java.io / java.time / java.util.concurrent 等 | 最常用;包含 java.lang |
import module java.sql; | java.sql + javax.sql | transitive requires 的导出也会进来 |
import module java.desktop; | java.awt / javax.swing / javax.imageio 等 | 与 java.base 可能产生同名类 |
import module java.xml; | javax.xml 及其子包 | 可合并多条 javax.xml.* 导入 |
- 只导入向当前模块导出的包;未导出包不可用。限定导出(exports ... to)只有在导出给当前模块时才可用,导出给其他模块的不会进入范围。
- 不能导入 classpath 上的未命名模块;模块导入只接受模块名。
- 不改变反射访问:没有 open,
setAccessible仍然受限。 - 在命名模块中,当前模块必须已经读取 M;import module 不会补上 requires。
模块导入是编译期的名字解析便利,不是运行时依赖声明。真正需要模块边界时,仍然要写 module-info 的 requires / exports。
同名类:模块导入会制造歧义03
模块导入是按需导入的放大版,因此两个模块导出同名简单类时,使用简单名会编译失败。最典型的例子是 java.util.List 与 java.awt.List。
import module java.base;
import module java.desktop;
public class AmbiguousList {
List<String> names;
}
AmbiguousList.java:5: error: reference to List is ambiguous
List<String> names;
^
both class java.awt.List in java.awt and interface java.util.List in java.util match
1 error
解决办法是加更具体的导入。单类型导入优先级最高;按需包导入次之;模块导入最弱。下面的代码用 import java.util.List; 消除了歧义。
import module java.base;
import module java.desktop;
import java.util.List;
public class ResolvedList {
public static void main(String[] args) {
List<String> names = List.of("a", "b");
System.out.println(names);
}
}
[a, b]
- 单类型导入 > 按需包导入 > 模块导入;后者会被前者遮蔽。
java.base+java.sql的Date、java.desktop内部的Element也会冲突。- 不用简单名(写全限定名)不会触发歧义。
- 同一个包被多条声明导入不会报错;冲突只发生在不同包的同名类型。
模块导入越多,歧义面越大。一个文件只导入真正使用的模块,并保留必要的单类型导入,可读性更好。
边界:不是模块化,也不是万能导入04
import module java.base 不会导入 java.sql,因为 java.sql 不是 java.base 导出的包。下面的编译错误说明「模块导入」只覆盖该模块及其传递读到的模块,不是全 JDK 导入。
import module java.base;
public class NoSql {
Connection connection;
}
NoSql.java:4: error: cannot find symbol
Connection connection;
^
symbol: class Connection
location: class NoSql
1 error
| 它能做 | 它不能做 |
|---|---|
| 减少同一模块多个包的 import 噪音 | 替代 module-info 的 requires |
| 在 classpath 代码中使用 JDK 模块 API | 导入 classpath 上的未命名模块 |
| 自动带上 transitive requires 的导出包 | 打开未导出包或获得反射权限 |
| 与普通 import 共存并参与遮蔽规则 | 改变运行时模块图或类加载 |
| 合并 javax.xml.* 等多条按需导入 | 导入未导出包或导出给其他模块的限定导出 |
在命名模块中,import module 的前提是当前模块已经读取目标模块;它不会隐式添加 requires。因此它不能用来「绕过」模块依赖声明,只是让已读模块的 API 更容易导入。
如果目标是强封装、可替换的模块边界,答案仍然是 module-info.java + requires/exports。模块导入只解决源码里的名字噪音。
落地:什么时候值得用05
| 场景 | 建议 | 原因 |
|---|---|---|
| 小工具大量使用 java.base | 用 import module java.base; | 省掉十几条 import |
| 教学 / 示例代码 | 可以用 | 读者不必先学包层级 |
| 只用到一两个类 | 用普通 import | 显式依赖更清楚 |
| 跨 java.base / java.desktop / java.sql | 谨慎,准备单类型导入 | 同名类冲突概率高 |
| 库的公开 API 源文件 | 显式 import | 让依赖关系可审计 |
- 按 JEP 511 的建议分组:模块导入在前,包按需导入居中,单类型导入在后。
- 代码审查时把模块导入当成「依赖声明」看:它扩大了可见名字集合。
- 从 24 预览迁到 25 正式时,去掉
--enable-preview;但 24 的预览 class 仍不能直接在 25 运行。 - IDE 的自动导入可能不认识模块导入;团队要统一格式,避免来回改。
模块导入在 23–24 是预览,25 正式。语法很小,但版本边界要记清:
--release 21 会直接报 module imports are not supported。版本边界BOUNDARY
这张表比 API 本身更值得记。写代码时先确认你的运行时版本。
| 特性 / API | 状态 | 说明 |
|---|---|---|
| 模块导入声明 | 23–24 预览 / 25 正式 | JEP 511;25 起无需 --enable-preview。 |
| transitive requires 的导出 | 25 包含 | java.sql 会连带 javax.sql。 |
| 未命名模块 | 不能导入 | import module 只接受模块名,不能导入 classpath。 |
| requires | 仍必须显式声明 | import module 不改变模块图。 |
| 限定导出 | 仅导出给当前模块时导入 | exports ... to 其他模块的包不会进入范围;必要时仍用传统导入。 |
| 同名类 | 编译期歧义 | 用单类型导入或按需包导入遮蔽。 |
费曼自检SELF CHECK
先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。
01. import module java.base; 导入了哪些包?
java.base 向当前模块导出的所有包,以及当前模块因读取 java.base 而读取到的模块所导出的包(transitive requires 链)。它不导入其他无关模块,例如 java.sql。
02. 模块导入能替代 requires 吗?
不能。它只是编译期的按需包导入,不改变运行时模块图,也不会让未读取的模块变得可读。命名模块仍需在 module-info 中声明 requires。
03. import module java.base; 与 import module java.desktop; 同时使用时,List 为什么会歧义?
java.util.List 与 java.awt.List 都是导出包中的公开类型,简单名 List 同时匹配两个导入。加
import java.util.List; 这样的单类型导入即可遮蔽模块导入。04. 模块导入能导入 classpath 上的第三方 jar 吗?
不能。import module 只接受模块名,不能引用未命名模块;classpath 上的非模块化 jar 仍需普通 import。
学习进度
记录保存在本机浏览器 · 不上传