java9新特性详解与实践

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

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():支持备选 Optional
  • stream():将 Optional 转换为 Stream
vbnet 复制代码
Optional<String> result = Optional.empty();
result.or(() -> Optional.of("备选值"));

总结

Java 9 的核心新特性大大提升了开发效率和代码质量,其中最重要的是:

  1. 模块系统 :提供了强大的封装和组件化能力,代表了 Java 平台的重大架构变革,本质是为了系统瘦身
  2. JShell:交互式编程环境使 Java 更加灵活易用,特别适合学习、探索和原型设计
  3. 其他便利特性如集合工厂方法、Stream API 增强、接口私有方法和 Optional 类增强
相关推荐
Easonmax2 分钟前
【JavaEE】网络原理详解
java·java-ee
java_学习爱好者13 分钟前
SpringBoot配置文件多环境开发
java
Asthenia041213 分钟前
Spring事件机制:微服务架构下的子服务内部解耦合/多场景代码分析
后端
别来无恙✲25 分钟前
SpringBoot启动方法分析
java·springboot·场景设计
Asthenia041226 分钟前
面试官问我:Spring AOP的代理模式与实现原理深度剖析
后端
Jay_See32 分钟前
Leetcode——239. 滑动窗口最大值
java·数据结构·算法·leetcode
小马爱打代码39 分钟前
Spring Boot - 实现邮件发送
spring boot·后端
褚翾澜40 分钟前
Ruby语言的代码重构
开发语言·后端·golang
DKPT40 分钟前
Eclipse,MyEclipse,IDEA,Vscode这些编译器和JDK的相爱相杀
java·eclipse·编辑器·intellij-idea·myeclipse
肠胃炎43 分钟前
真题246—矩阵计数
java·线性代数·算法·矩阵·深度优先