Java 9 于 2017年9月发布,引入了模块系统这一重大特性,同时也带来了许多实用的改进。虽然模块系统在企业中应用不多,但集合工厂方法等特性却非常实用
正式特性
【了解】模块系统
在模块系统出现之前,传统 Java 应用只能依赖 classpath 来管理依赖,所有的类都在同一个类路径下,任何类都可以访问任何其他类,这种 "全局可见性" 在大型项目中会导致代码耦合严重、依赖关系混乱、运行时才发现 ClassNotFoundException 等问题。
模块系统允许我们将代码组织成模块,每个模块都有明确的依赖关系和导出接口,让大型应用的架构变得更加清晰和可维护。
模块系统通过 module-info.java 文件来定义模块的边界,明确声明哪些包对外开放,哪些依赖是必需的,这样就形成了强封装的架构。
比如一个用户管理模块只暴露用户服务接口,而内部的数据访问层对其他模块完全不可见,这种设计让系统的层次结构更加清晰,也避免了意外的跨层调用。
module user.management {
// 只导出 service 包,dao 包对外不可见
exports com.company.user.service;
// 依赖其他模块
requires java.base;
requires database.connection;
}此外,模块系统还带来了更好的性能优化,JVM 可以在启动时只加载必需的模块,减少内存占用和启动时间(适合云原生应用)。
但是,模块系统在企业中用的比较少,目前大多数企业还是使用传统的 Maven/Gradle + JAR 包的方式管理依赖,改造项目的成本 > 模块系统带来的实际收益,所以仅作了解就好。
【必备】集合工厂方法
Java 9 为集合类添加了便捷的工厂方法,能够轻松创建不可变集合。
在这之前,创建不可变集合还是比较麻烦的,很多开发者会选择依赖第三方库(比如 Google Guava)。
传统的不可变集合创建方式:
// Java 9 之前创建不可变集合的方式
List<String> oldList = new ArrayList<>();
oldList.add("苹果");
oldList.add("香蕉");
oldList.add("鱼皮");
List<String> immutableList = Collections.unmodifiableList(oldList);
// 或者使用 Google Guava 等第三方库
List<String> guavaList = ImmutableList.of("苹果", "香蕉", "鱼皮");有了 Java 9 的工厂方法,创建不可变集合简直不要太简单!
// Java 9 的简洁写法
List<String> fruits = List.of("苹果", "香蕉", "鱼皮");
Set<Integer> numbers = Set.of(1, 2, 3, 4, 5);
Map<String, Integer> scores = Map.of(
"张三", 85,
"鱼皮", 92,
"狗剩", 78
);这些集合是真正不可变的,任何修改操作都会抛出 UnsupportedOperationException 异常。

如果想创建包含大量元素的不可变 Map,可以使用 ofEntries 方法:
Map<String, String> largeMap = Map.ofEntries(
Map.entry("key1", "value1"),
Map.entry("key2", "value2"),
Map.entry("key3", "value3")
// ... 可以有任意多个
);【了解】JShell 交互工具
JShell 是 Java 9 引入的一个交互式工具,在这个工具出现之前,我们要测试一小段 Java 代码,必须创建完整的类和 main 方法,编译后才能运行。
有了 JShell,我们可以像使用 Python 解释器一样使用 Java,对于学习调试有点儿用(但不多)。
直接在命令行输入 jshell 就能使用了:

【了解】HTML5 Javadoc
Java 9 更新了 Javadoc 工具,生成的文档符合 HTML5 标准,提供了更好的可访问性和现代化的外观:
/**
* 计算圆的面积
*
* @param radius 圆的半径,必须大于 0
* @return 圆的面积
* @throws IllegalArgumentException 如果半径小于等于 0
* @since 9
*/
public double calculateArea(double radius) {
if (radius <= 0) {
throw new IllegalArgumentException("半径必须大于 0");
}
return Math.PI * radius * radius;
}生成的 HTML5 文档支持更好的搜索功能和响应式设计。
【了解】Javadoc 搜索
Java 9 的 Javadoc 增加了搜索功能,可以快速查找类、方法、字段等:
支持模糊搜索
支持按类型过滤(类、方法、字段等)
提供搜索建议和自动补全
【了解】紧凑字符串
Java 9 优化了 String 类的内部实现。之前 String 内部使用 char[] 数组存储字符,每个字符占用 2 字节。
Java 9 引入了紧凑字符串特性,String 内部改用 byte[] 数组 + 编码标志:
public final class String {
private final byte[] value; // 字符数据
private final byte coder; // 编码标志:0=LATIN1, 1=UTF16
// ... 其他代码
}如果字符串只包含 Latin1 字符(ASCII),每个字符只占用 1 字节
如果包含其他 Unicode 字符,才使用 UTF-16 编码,每个字符占用 2 字节
这个优化对开发者完全透明,可以节省大约 10-15% 的堆内存使用。
【了解】Stack-Walking API
Java 9 提供了新的 Stack-Walking API,可以更高效地遍历调用栈:
import java.lang.StackWalker;
import java.util.List;
import java.util.stream.Collectors;
public class StackWalkingExample {
public static void main(String[] args) {
methodA();
}
static void methodA() {
methodB();
}
static void methodB() {
// 获取调用栈信息
StackWalker walker = StackWalker.getInstance();
// 打印调用栈
walker.forEach(frame ->
System.out.println(frame.getClassName() + "." + frame.getMethodName())
);
// 获取调用者信息
StackWalker.StackFrame caller = walker.getCallerFrame();
System.out.println("调用者:" + caller.getMethodName());
// 获取指定深度的栈帧
List<String> methods = walker.walk(frames ->
frames.limit(3)
.map(StackWalker.StackFrame::getMethodName)
.collect(Collectors.toList())
);
System.out.println("方法调用链:" + methods);
}
}相比传统的 Thread.currentThread().getStackTrace(),新的 API 性能更好,功能更强大。
【了解】更多并发更新
Java 9 对并发工具类进行了增强:
CompletableFuture 增强
// 支持延迟执行
CompletableFuture<String> future = CompletableFuture
.delayedExecutor(1, TimeUnit.SECONDS)
.execute(() -> System.out.println("延迟执行"));
// 支持超时处理
CompletableFuture<String> timeoutFuture = CompletableFuture
.supplyAsync(() -> {
// 模拟长时间运行的任务
try { Thread.sleep(2000); } catch (InterruptedException e) {}
return "完成";
})
.orTimeout(1, TimeUnit.SECONDS) // 1秒超时
.exceptionally(throwable -> "超时了");Flow API
Java 9 引入了响应式流(Reactive Streams)的标准实现:
import java.util.concurrent.Flow;
import java.util.concurrent.SubmissionPublisher;
public class FlowExample {
public static void main(String[] args) throws InterruptedException {
// 创建发布者
SubmissionPublisher<String> publisher = new SubmissionPublisher<>();
// 创建订阅者
Flow.Subscriber<String> subscriber = new Flow.Subscriber<String>() {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
subscription.request(1); // 请求一个元素
}
@Override
public void onNext(String item) {
System.out.println("接收到:" + item);
subscription.request(1); // 请求下一个元素
}
@Override
public void onError(Throwable throwable) {
throwable.printStackTrace();
}
@Override
public void onComplete() {
System.out.println("完成");
}
};
// 订阅
publisher.subscribe(subscriber);
// 发布数据
publisher.submit("Hello");
publisher.submit("World");
publisher.close();
Thread.sleep(1000);
}
}接口私有方法
【了解】接口私有方法
思考一个问题,如果某个接口中的默认方法需要复用代码,你会怎么做呢?
比如让你来优化下面这段代码:
public interface Calculator {
default double calculateRectangleArea(double width, double height) {
// 重复的验证逻辑
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("宽度和高度必须为正数");
}
return width * height;
}
default double calculateTriangleArea(double base, double height) {
// 重复的验证逻辑
if (base <= 0 || height <= 0) {
throw new IllegalArgumentException("底边和高度必须为正数");
}
return base * height / 2;
}
}你会把重复的验证逻辑写在哪里呢?
答案很简单,写在一个外部工具类里,或者在接口内再写一个通用的验证方法:
public interface Calculator {
// 通用的验证方法
default void validate(double x, double y) {
if (x <= 0 || y <= 0) {
throw new IllegalArgumentException("必须为正数");
}
}
}但这种方式存在一个问题,validate 作为 default 方法,它会成为接口的公共 API,所有实现类都能访问到!其实这个方法只需要在接口内可以使用就够了。
Java 9 解决了这个问题,允许在接口中定义私有方法(以及私有静态方法)。
public interface Calculator {
// 私有方法
private void validate(double x, double y) {
if (x <= 0 || y <= 0) {
throw new IllegalArgumentException("必须为正数");
}
}
// 私有静态方法
private static void validatePositive(double x, double y) {
if (x <= 0 || y <= 0) {
throw new IllegalArgumentException("必须为正数");
}
}
}这样一来,接口内部可以优雅地复用代码,同时保持接口对外的简洁性。
💡 这里也能看出 Java 的演进很谨慎,先允许 default 方法(Java 8),再允许 private 方法(Java 9),每一步都有明确的设计考量。
【了解】改进的 try-with-resources
Java 9 改进了 try-with-resources 语句,在这之前,我们不能在 try 子句中使用外部定义的变量,必须在 try 括号内重新声明,会让代码变得冗余。
// Java 9 之前
public void readFile(String filename) throws IOException {
BufferedReader reader = Files.newBufferedReader(Paths.get(filename));
try (BufferedReader br = reader) { // 需要重新赋值
br.lines().forEach(System.out::println);
}
}Java 9 的改进让代码更加简洁:
// Java 9
public void readFile(String filename) throws IOException {
BufferedReader reader = Files.newBufferedReader(Paths.get(filename));
try (reader) { // 直接使用 effectively final 变量
reader.lines().forEach(System.out::println);
}
}而且还可以同时使用多个变量:
public void processFiles(String file1, String file2) throws IOException {
var reader1 = Files.newBufferedReader(Paths.get(file1));
var reader2 = Files.newBufferedReader(Paths.get(file2));
try (reader1; reader2) { // 可以使用多个变量
String line1 = reader1.readLine();
String line2 = reader2.readLine();
while (line1 != null && line2 != null) {
System.out.println(line1 + " | " + line2);
line1 = reader1.readLine();
line2 = reader2.readLine();
}
}
}其他改进
【了解】Stream API 增强
Java 9 为 Stream API 添加了几个实用方法:
// takeWhile:获取满足条件的元素直到遇到不满足的
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8);
List<Integer> taken = numbers.stream()
.takeWhile(n -> n < 5) // 取小于5的元素
.collect(Collectors.toList());
System.out.println(taken); // [1, 2, 3, 4]
// dropWhile:跳过满足条件的元素直到遇到不满足的
List<Integer> dropped = numbers.stream()
.dropWhile(n -> n < 5) // 跳过小于5的元素
.collect(Collectors.toList());
System.out.println(dropped); // [5, 6, 7, 8]
// ofNullable:安全地创建可能为null的流
String nullString = null;
long count = Stream.ofNullable(nullString).count();
System.out.println(count); // 0
// iterate:带条件的无限流
List<Integer> evenNumbers = Stream.iterate(0, n -> n < 10, n -> n + 2)
.collect(Collectors.toList());
System.out.println(evenNumbers); // [0, 2, 4, 6, 8]【了解】Optional 增强
Optional 也得到了一些有用的增强:
Optional<String> optional1 = Optional.of("Hello");
Optional<String> optional2 = Optional.empty();
// ifPresentOrElse:有值时执行一个动作,没值时执行另一个动作
optional1.ifPresentOrElse(
value -> System.out.println("有值:" + value),
() -> System.out.println("没有值")
);
// or:提供备选的Optional
Optional<String> result = optional2.or(() -> Optional.of("默认值"));
// stream:将Optional转换为Stream
List<String> values = List.of(optional1, optional2).stream()
.flatMap(Optional::stream)
.collect(Collectors.toList());
System.out.println(values); // [Hello]总结
Java 9 虽然引入了模块系统这一重大特性,但在实际企业开发中应用并不广泛。不过,集合工厂方法、JShell、接口私有方法等特性都很实用,特别是集合工厂方法大大简化了不可变集合的创建。Stream 和 Optional 的增强也让函数式编程更加完善。
Java 9 标志着 Java 开始采用 6 个月一次的发布周期,后续版本的迭代速度明显加快。