java版本越来越高,但是由于很多历史原因,公司还是使用了java8,但是作为程序员还是要接触java的新特性,基于这点打算梳理一下每一期java的新特性,用于自我记录。重点详解模块系统和 JShell 工具,并简要介绍其他几个核心新特性,包括集合工厂方法、Stream API 增强、接口私有方法和 Optional 类增强。
1. 模块系统 (Project Jigsaw)
Java 9 最重要的变化是引入了模块系统,这是 Java 平台自诞生以来最重大的架构变革之一。
1.1 为什么需要模块系统?
在 Java 9 之前,Java 应用程序主要依赖于 monolithic(单体)的 JDK/JRE 和庞大的 classpath 机制,这带来了一系列问题:
- JAR 地狱:无法有效处理依赖冲突
- 封装性弱:无法真正隐藏内部 API
- 应用程序过大:无法只选择需要的 JDK 部分
- 启动性能:需要扫描整个 classpath 上的所有类
- 安全风险:内部 API 的意外暴露
1.2 模块系统的核心概念
1.2.1 什么是模块?
模块(Module)是一个命名的、自描述的代码和数据集合。它包含:
- 名称:全局唯一的模块标识符
- 依赖关系:明确声明对其他模块的依赖
- 公共 API:显式声明对外暴露的包
- 服务:可提供和消费的服务
- 资源:模块中包含的非代码资源
1.2.2 模块系统的核心特性
- 强封装:不仅隐藏类,还隐藏包;默认情况下,模块中的所有包都是模块私有的
- 可靠配置:编译时和运行时都会验证模块依赖关系
- 模块化 JDK:Java 平台本身被拆分为 94 个模块,形成模块图结构
- 更好的平台完整性:强化内部 API 的隐藏(限制对 sun.*等内部 API 的访问)
- 自定义运行时:通过 jlink 工具创建包含所需模块的定制运行时
1.3 模块描述符详解
模块系统的核心是module-info.java
文件,它定义了模块的所有特性。
java
module com.example.myapp {
// 导出包,使其可被其他模块访问
exports com.example.myapp.api;
// 有条件地导出包,只对特定模块开放
exports com.example.myapp.internal to com.example.plugin;
// 声明依赖的其他模块
requires java.base; // 所有模块默认依赖java.base
requires java.logging;
// 声明传递依赖(可被依赖此模块的模块看到)
requires transitive java.sql;
// 声明使用的服务
uses com.example.service.MyService;
// 声明提供的服务实现
provides com.example.service.MyService
with com.example.myapp.service.MyServiceImpl;
// 允许反射访问
opens com.example.myapp.reflect;
// 有条件地允许反射访问
opens com.example.myapp.annotation to java.annotation;
}
1.4 实际模块化应用开发示例
下面是一个完整的模块化应用程序示例,包含多个模块:
1.4.1 项目结构
bash
my-modular-app/
├── src/
│ ├── com.app.main/ # 主应用模块
│ │ ├── module-info.java
│ │ └── com/app/main/
│ │ └── Main.java
│ │
│ ├── com.app.service/ # 服务API模块
│ │ ├── module-info.java
│ │ └── com/app/service/
│ │ └── GreetingService.java
│ │
│ └── com.app.service.impl/ # 服务实现模块
│ ├── module-info.java
│ └── com/app/service/impl/
│ └── GreetingServiceImpl.java
1.4.2 模块定义
服务 API 模块 (com.app.service/module-info.java) :
java
module com.app.service {
exports com.app.service;
}
服务实现模块 (com.app.service.impl/module-info.java) :
arduino
module com.app.service.impl {
requires com.app.service;
provides com.app.service.GreetingService
with com.app.service.impl.GreetingServiceImpl;
}
主应用模块 (com.app.main/module-info.java) :
arduino
module com.app.main {
requires com.app.service;
uses com.app.service.GreetingService;
}
1.4.3 服务接口和实现
GreetingService.java:
csharp
package com.app.service;
public interface GreetingService {
String getGreeting();
}
GreetingServiceImpl.java:
typescript
package com.app.service.impl;
import com.app.service.GreetingService;
public class GreetingServiceImpl implements GreetingService {
@Override
public String getGreeting() {
return "Hello from Modular Java 9!";
}
}
Main.java:
arduino
package com.app.main;
import com.app.service.GreetingService;
import java.util.ServiceLoader;
public class Main {
public static void main(String[] args) {
// 使用ServiceLoader加载服务实现
ServiceLoader<GreetingService> services = ServiceLoader.load(GreetingService.class);
// 获取服务实现并调用
services.findFirst().ifPresent(service ->
System.out.println(service.getGreeting())
);
}
}
1.4.4 编译与运行
bash
# 编译服务API模块
javac -d mods/com.app.service src/com.app.service/module-info.java src/com.app.service/com/app/service/GreetingService.java
# 编译服务实现模块
javac --module-path mods -d mods/com.app.service.impl src/com.app.service.impl/module-info.java src/com.app.service.impl/com/app/service/impl/GreetingServiceImpl.java
# 编译主应用模块
javac --module-path mods -d mods/com.app.main src/com.app.main/module-info.java src/com.app.main/com/app/main/Main.java
# 运行应用
java --module-path mods -m com.app.main/com.app.main.Main
1.5 使用 jlink 创建自定义运行时镜像
Java 9 引入了 jlink 工具,可以创建包含应用所需模块的定制运行时镜像,大大减小应用分发大小:
bash
# 创建包含所需模块的自定义运行时
jlink --module-path mods:$JAVA_HOME/jmods --add-modules com.app.main,com.app.service,com.app.service.impl --output custom-runtime
# 使用自定义运行时运行应用
custom-runtime/bin/java -m com.app.main/com.app.main.Main
这个自定义运行时只包含应用程序所需的模块,大小可能只有完整 JRE 的一小部分,特别适合容器化部署或资源受限环境。
1.6 模块系统的实际应用场景
- 微服务架构:每个服务作为独立模块,明确 API 边界
- IoT 设备:创建最小运行时,减少资源消耗
- 企业应用:改进大型代码库的组织和维护
- 框架和库开发:明确公共 API 与内部实现的边界
- 插件架构:使用服务加载器实现松耦合扩展
1.7 模块系统的优势与挑战
优势:
- 提高应用程序性能和安全性
- 减小运行时大小(通过自定义运行时)
- 提高代码的可维护性和可伸缩性
- 更好的依赖管理和版本控制
- 实现真正的封装
挑战:
- 与现有非模块化代码的兼容性
- 学习曲线和迁移成本
- 某些工具和框架的适配问题
- 反射操作的复杂性增加
2. 集合工厂方法
Java 9 简化了不可变集合的创建方式,引入了便捷的工厂方法。
2.1 新的工厂方法
List.of()
Set.of()
Map.of()
和Map.ofEntries()
2.2 实践示例
ini
// Java 9
List<String> languages = List.of("Java", "Python", "Go");
Set<Integer> numbers = Set.of(1, 2, 3, 4, 5);
Map<String, Integer> scores = Map.of("Alice", 95, "Bob", 88);
3. Stream API 增强
Java 8 引入的 Stream API 在 Java 9 中得到了进一步增强。
3.1 新增方法
takeWhile()
:依次获取满足条件的元素dropWhile()
:依次丢弃满足条件的元素ofNullable()
:处理可能为 null 的情况iterate()
:支持终止条件的迭代器
4. 接口私有方法
Java 9 允许在接口中定义私有方法,增强了代码复用性。
typescript
public interface Logger {
void log(String message);
default void logInfo(String message) {
log(addSeverity("INFO", message));
}
// 私有辅助方法
private String addSeverity(String severity, String message) {
return "[" + severity + "] " + message;
}
}
5. JShell - 交互式 Java 编程环境详解
JShell 是 Java 9 引入的一个革命性工具,它为 Java 语言增加了 REPL(Read-Eval-Print Loop)能力,使得 Java 能像脚本语言一样交互式执行代码。这为学习、教学、探索 API 和原型设计带来了革命性变化。
5.1 JShell 的核心功能
- 代码片段执行:无需类定义和 main 方法
- 实时语法检查:编写过程中实时提供反馈
- 自动导入:常用包自动导入(java.lang.*, java.util.*等)
- 变量和方法定义:定义临时变量和方法
- 类型推断:自动推断变量类型(var 类似)
- 代码补全:按 Tab 键自动补全代码
- 历史记录:记住之前输入的代码并允许重用
- 代码片段重做:修改和重新评估之前的片段
5.2 启动和基本设置
启动 JShell 有多种方式:
ini
# 基本启动
jshell
# 启动时导入额外包
jshell --add-modules=<模块名称>
# 使用特定版本的Java
JAVA_HOME=/path/to/java9 jshell
# 启动时执行脚本
jshell <script-file-name>.jsh
自定义设置和首选项:
shell
jshell> /set feedback verbose # 设置详细反馈模式
jshell> /set mode normal # 设置标准反馈模式
jshell> /set editor vim # 设置外部编辑器
5.3 JShell 命令全解析
JShell 提供了丰富的内置命令,所有命令都以斜杠(/)开头:
基本命令:
/exit
或/quit
- 退出 JShell/help
- 显示帮助信息/help <command>
- 显示特定命令的帮助
代码管理命令:
/list
- 列出所有已输入的代码片段/list <id>
- 列出特定 ID 的代码片段/edit
- 在外部编辑器中编辑代码/drop <id>
- 删除特定 ID 的代码片段/save <file>
- 保存当前会话到文件/open <file>
- 从文件加载代码片段
检查环境命令:
/vars
- 列出所有变量/methods
- 列出所有方法/types
- 列出所有类型定义/imports
- 列出所有导入/env
- 显示执行环境信息
执行控制命令:
/reset
- 重置 JShell 状态,清除所有代码片段/reload
- 重新加载所有片段(有用于清除错误)
反馈控制命令:
/set feedback <mode>
- 设置反馈模式(silent, concise, normal, verbose)/set format <mode>
- 设置输出格式/set truncation <length>
- 设置输出截断长度
5.4 高级 JShell 使用技巧
5.4.1 处理多行代码
JShell 支持多行输入,自动检测未完成的语句:
javascript
jshell> for (int i = 1; i <= 5; i++) {
...> System.out.println("Number: " + i);
...> }
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5
5.4.2 使用外部编辑器
对于复杂代码,可以使用外部编辑器:
javascript
jshell> /edit // 打开编辑器编辑当前片段
jshell> /edit 3 // 编辑ID为3的片段
5.4.3 处理异常和错误
JShell 能优雅处理异常:
csharp
jshell> int x = 10 / 0
| Exception java.lang.ArithmeticException: / by zero
| at (#1:1)
jshell> try {
...> int y = 10 / 0;
...> } catch (Exception e) {
...> System.out.println("捕获到异常: " + e.getMessage());
...> }
捕获到异常: / by zero
5.4.4 创建和测试自定义类
JShell 支持定义完整类:
arduino
jshell> class Person {
...> private String name;
...> private int age;
...>
...> public Person(String name, int age) {
...> this.name = name;
...> this.age = age;
...> }
...>
...> public String toString() {
...> return name + ", " + age + "岁";
...> }
...> }
| 已创建 类 Person
jshell> var person = new Person("张三", 25)
person ==> 张三, 25岁
5.4.5 探索 API
JShell 非常适合探索不熟悉的 API:
scss
// 导入并尝试新API
jshell> import java.util.concurrent.Flow
// 查看类成员
jshell> Flow.Subscriber
Flow$Subscriber
// 查看方法签名
jshell> /methods Flow.Subscriber
5.4.6 与 Java 9 新特性集成
JShell 可以方便测试 Java 9 的其他新特性:
scss
// 测试集合工厂方法
jshell> var list = List.of(1, 2, 3, 4)
list ==> [1, 2, 3, 4]
// 测试Stream新方法
jshell> list.stream().takeWhile(n -> n < 3).collect(Collectors.toList())
$2 ==> [1, 2]
// 测试Optional新方法
jshell> Optional.empty().orElseThrow(() -> new RuntimeException("空值"))
| Exception java.lang.RuntimeException: 空值
| at (#3:1)
5.5 JShell 脚本
JShell 支持创建可重用的脚本文件(.jsh):
demo.jsh:
csharp
// 定义实用工具方法
void printHeader(String header) {
System.out.println("\n===== " + header + " =====");
}
// 测试集合工厂方法
printHeader("集合工厂方法");
var list = List.of("a", "b", "c");
System.out.println(list);
// 测试Optional
printHeader("Optional新方法");
Optional.of("测试").ifPresentOrElse(
v -> System.out.println("值: " + v),
() -> System.out.println("无值")
);
然后执行脚本:
ini
$ jshell demo.jsh
===== 集合工厂方法 =====
[a, b, c]
===== Optional新方法 =====
值: 测试
5.6 实际应用场景
JShell 在以下场景特别有用:
- 学习 Java 或者新 API:快速尝试和理解
- 教学和培训:即时演示概念
- API 探索和原型设计:探索不熟悉的 API
- 算法测试:快速验证算法逻辑
- 调试支持:类似于浏览器的控制台
- 快速数据探索:处理和检查数据
5.7 JShell 的内部机制
JShell 内部使用 Java 编译器 API(javax.tools)和 JDI(Java Debugger Interface)来实现其功能。它将片段转换为可执行代码,并使用特殊类装载器动态加载。
JShell 会话在内部维护一个"状态",包括所有定义的变量、方法、类以及导入,使后续输入可以引用先前定义的元素。
6. Optional 类增强
Java 8 引入的 Optional 类在 Java 9 中得到了增强,添加了三个实用方法:
ifPresentOrElse()
:提供缺失值的处理or()
:支持备选 Optionalstream()
:将 Optional 转换为 Stream
vbnet
Optional<String> result = Optional.empty();
result.or(() -> Optional.of("备选值"));
总结
Java 9 的核心新特性大大提升了开发效率和代码质量,其中最重要的是:
- 模块系统 :提供了强大的封装和组件化能力,代表了 Java 平台的重大架构变革,本质是为了系统瘦身
- JShell:交互式编程环境使 Java 更加灵活易用,特别适合学习、探索和原型设计
- 其他便利特性如集合工厂方法、Stream API 增强、接口私有方法和 Optional 类增强