JDK JDK 学习手册8 → 27 · JDK 专用学习站
QUICKREF · MODULES · JEP 511

Module Import Declarations:一行导入一个模块

JDK 25 · 速查主题 04 / 06 · 预计 25 分钟
核心25 分钟JEP 511
一句话import module java.base; 把 java.base 导出的所有包一次性导入,等价于几十条按需包导入。它解决的是 import 噪音,不是模块化:不改变 requires,不打开封装,也不能导入 classpath 上的未命名模块。

学完你要能自己回答

  1. import module M 到底导入了哪些包?transitive requires 算不算?
  2. 两个模块有同名类时怎么办?单类型导入与按需导入谁遮蔽谁?
  3. 为什么 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.sqltransitive 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。
学习进度 记录保存在本机浏览器 · 不上传

JDK 学习手册 · JDK / Java 专用学习站 · 纯静态 · 双击 index.html 即可使用

本站专注 JDK 版本演进与 Java 教学,非 Oracle / OpenJDK 官方站点。版本数据来自 OpenJDK 官方 Features 表;课程示例已在对应本机 JDK 实测。构建日期 2026-11-12。