ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

02-JDK 9 模块化与 JShell:项目结构将彻底改变

02-JDK 9 模块化与 JShell:项目结构将彻底改变

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,应用边界也更清晰。但现实骨感——


二、为什么模块化"叫好不叫座"

  1. 迁移成本太高:老项目成千上万个类,要一个个拆模块、理顺requires/exports,工作量巨大。
  2. 生态没跟上:大量第三方库(早期版本)没写module-info,放进模块路径会各种告警,于是大家干脆继续用 classpath。
  3. 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 / 加强 G1G1 成为默认 GC(之前是 Parallel GC)
紧凑字符串预热String压缩在 9 完成,见下篇

七、本篇记住的清单

  1. JPMS 用module-info.java做强封装 + 可靠依赖,主要被 JDK 自身采纳,应用落地少。
  2. 模块化"叫好不叫座":迁移贵、生态滞后、classpath 兼容模式还在。
  3. JShell 是官方 REPL,验证代码零成本。
  4. 接口可写私有方法,给default方法抽公共逻辑。
  5. List.of/Set.of/Map.of返回不可变集合,比包装更彻底。
  6. Stream.ofNullable/iterate(带终止)/takeWhile等小增强很实用。
  7. JDK 9 起 G1 成默认 GC,HTTP/2 客户端进孵化。

JDK 9 是"底层重构 + 甜点补给"的一版。下一篇看 JDK 10/11:一个让代码变短的var,和一个决定你升级节奏的 LTS 概念。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进