JDK 8 把写法范式定下来了,JDK 9 干的是另一件大事——给 JDK 自身和你的应用做"模块化"。这是 Java 历史上改动最底层的一次发布(连 JDK 内部都拆成了几十个模块),也带来了程序员能直接玩的两个甜点:JShell 和一堆语法糖。
这一篇讲清楚三件事:模块化(JPMS)到底改了什么、为什么它"叫好不叫座"、以及 JShell 和那些小特性怎么帮你提效。
一、JPMS:Java 平台模块系统
JDK 9 之前,所有类都在一个巨大的 classpath 上"自由恋爱"——任何代码能new任何类,连 JDK 内部的sun.misc.Unsafe都被各种框架偷偷调用,JDK 团队想改内部 API 寸步难行。模块化就是要把这种混乱收口。
模块用module-info.java声明:
modulecom.example.app{requiresjava.sql;// 依赖哪个模块requirestransitivecom.example.lib;// 传递依赖exportscom.example.app.api;// 只暴露这个包,别的包外部看不到openscom.example.app.dto;// 反射开放(序列化/框架用)}核心规则:
- 强封装:没
exports的包,外部代码编译期都碰不到(连反射默认也不行,需opens)。 - 可靠配置:启动时就检查依赖是否齐全,而不是运行时才
ClassNotFoundException。 - 可见性收敛:
requires必须显式声明,不再有"隐式万能 classpath"。
理想很丰满:强封装能让 JDK 放心删掉/改写内部 API,应用边界也更清晰。但现实骨感——
二、为什么模块化"叫好不叫座"
- 迁移成本太高:老项目成千上万个类,要一个个拆模块、理顺
requires/exports,工作量巨大。 - 生态没跟上:大量第三方库(早期版本)没写
module-info,放进模块路径会各种告警,于是大家干脆继续用 classpath。 - classpath 仍保留:为了兼容,JDK 9 同时保留 classpath 模式,结果 99% 的项目还是 “JDK 9 跑着 JDK 8 的用法”,模块化形同虚设。
所以现实里,模块化主要被JDK 自身采纳(JDK 被拆成java.base、java.sql等模块,启动时可以只带用到的模块做瘦身),应用层落地很少。学它,重点在"理解 JDK 内部边界"和"需要时知道有这能力",不必强求给每个项目上模块。
三、JShell:官方 REPL 终于来了
这是 JDK 9 给普通开发者最实在的礼物——一个开箱即用的交互式命令行环境,不用再写public static void main就能试代码:
$ jshell jshell> int x = 1 + 1 x ==> 2 jshell> List.of(1,2,3).stream().map(n -> n*2).toList() $2 ==> [2, 4, 6] jshell> /vars // 看所有变量 jshell> /methods // 看所有方法 jshell> /edit 1 // 编辑第 1 段适用场景:快速验证 API 用法、现场算个表达式、教学演示。配合/save/open还能把片段存成脚本。虽然不如 IDE 强大,但胜在零依赖、随用随走。
四、接口私有方法
JDK 8 给了default方法,JDK 9 又补上接口私有方法,让多个default方法能抽取公共逻辑而不暴露给外部:
interfaceLogger{defaultvoidinfo(Strings){log("INFO",s);}defaultvoidwarn(Strings){log("WARN",s);}privatevoidlog(Stringlevel,Strings){// 接口内部的私有方法System.out.println("["+level+"] "+s);}}五、Stream 与集合的小增强
JDK 9 给集合和 Stream 加了几个实用方法:
// 工厂方法:不可变集合一行搞定(JDK 8 还要 Arrays.asList + 包装)List<String>list=List.of("a","b","c");Set<Integer>set=Set.of(1,2,3);Map<String,Integer>map=Map.of("a",1,"b",2);// Stream 新增Stream.ofNullable(maybeNull).forEach(System.out::println);// 空值直接跳过Stream.iterate(0,n->n<10,n->n+1);// 带终止条件的迭代List<Integer>taken=list.stream().takeWhile(n->n<3).toList();// 遇到不满足就停注意List.of返回的是不可变集合,调用add会抛UnsupportedOperationException——这是"不可变优先"理念的开始,比Collections.unmodifiableList更彻底(后者只是包装,原 list 还能改)。
六、其他值得知道的变化
| 特性 | 说明 |
|---|---|
| 多版本 JAR | 同一个 JAR 可在不同 JDK 下提供不同 class |
| 进程 API 增强 | ProcessHandle能拿 PID、父进程、子进程 |
| 新版 HTTP/2 客户端(孵化) | 叫jdk.incubator.http,JDK 11 转正为java.net.http |
| 废弃 Applet / 加强 G1 | G1 成为默认 GC(之前是 Parallel GC) |
| 紧凑字符串预热 | String压缩在 9 完成,见下篇 |
七、本篇记住的清单
- JPMS 用
module-info.java做强封装 + 可靠依赖,主要被 JDK 自身采纳,应用落地少。 - 模块化"叫好不叫座":迁移贵、生态滞后、classpath 兼容模式还在。
- JShell 是官方 REPL,验证代码零成本。
- 接口可写私有方法,给
default方法抽公共逻辑。 List.of/Set.of/Map.of返回不可变集合,比包装更彻底。Stream.ofNullable/iterate(带终止)/takeWhile等小增强很实用。- JDK 9 起 G1 成默认 GC,HTTP/2 客户端进孵化。
JDK 9 是"底层重构 + 甜点补给"的一版。下一篇看 JDK 10/11:一个让代码变短的var,和一个决定你升级节奏的 LTS 概念。