大家好!今天我要和大家分享 Java 9 中那些真正改变我们编码方式的新特性。作为 Java 开发者,了解这些新功能不仅能让你的代码更简洁、更高效,还能帮助你在团队中脱颖而出。

Java 9 于 2017 年 9 月发布,它带来了自 Java 8 以来最重大的架构变革。与 Java 8 注重语法层面的革新(如 Lambda 表达式)不同,Java 9 更关注基础设施和平台级别的改进,为大型应用开发、微服务架构和模块化编程提供了强大支持。

废话不多说,让我们直接进入正题!

1. 模块系统(Project Jigsaw)

Java 9 最重要的变革无疑是引入了模块系统,这是 Java 平台级别的重大架构改变。

什么是模块系统?

模块系统允许我们将代码组织成更高级别的组件(称为模块),每个模块都明确声明自己的依赖关系和对外公开的 API。

graph TD
    A[应用程序] --> B[模块A]
    A --> C[模块B]
    B -->|requires| D[模块C]
    B -->|requires| E[Java平台模块]
    C -->|requires| E
    B -->|exports| F[公开API]
    D -->|exports| G[公开API]
    style E fill:#f9f,stroke:#333
    style F fill:#afa,stroke:#333
    style G fill:#afa,stroke:#333

使用案例

让我们创建一个简单的模块化应用程序:

// 在module-info.java文件中定义模块
module com.example.myapp {
    requires java.base;  // 隐式依赖,可省略
    requires java.logging;
    requires transitive java.sql;  // 传递依赖,使用此模块的模块也能访问java.sql

    exports com.example.myapp.api;  // 只导出API包
    opens com.example.myapp.model to java.persistence;  // 仅向特定模块开放反射访问
}
// com.example.myapp.api包中的公共API
package com.example.myapp.api;

public class ServiceAPI {
    public String getServiceInfo() {
        return "Service Information";
    }
}
// com.example.myapp.internal包中的内部实现
package com.example.myapp.internal;

import java.util.logging.Logger;

class ServiceImplementation {
    private static final Logger LOGGER = Logger.getLogger(ServiceImplementation.class.getName());

    void processData() {
        LOGGER.info("Processing data...");
        // 实现细节
    }
}

使用 Maven 构建模块化项目

<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>myapp</artifactId>
    <version>1.0.0</version>

    <properties>
        <maven.compiler.source>9</maven.compiler.source>
        <maven.compiler.target>9</maven.compiler.target>
        <maven.compiler.release>9</maven.compiler.release>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.11.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <release>9</release>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

使用 jlink 创建自定义运行时

jlink --module-path $JAVA_HOME/jmods:target/modules --add-modules com.example.myapp --launcher myapp=com.example.myapp/com.example.myapp.Main --output myapp-image

上述命令创建一个只包含应用程序所需模块的自定义 JRE,大小可能只有标准 JRE 的一小部分。

模块系统优势分析

  1. 强封装性:外部代码无法访问未明确导出的包,提高代码安全性
  2. 明确依赖:模块必须声明其依赖关系,消除"classpath 地狱"
  3. 更小的运行时映像:使用jlink可以创建仅包含所需模块的自定义运行时
  4. 提高平台完整性:JDK 本身被模块化,增强了安全性
  5. 更好的性能:启动时间更短,内存占用更少

注意事项与局限性

  1. 遗留代码兼容性:旧代码需作为未命名模块放在类路径上,不能充分利用模块系统优势
  2. 迁移复杂性:大型项目迁移到模块系统可能需要大量重构
  3. 依赖爆炸:如果不谨慎管理传递依赖,可能导致模块图变得复杂
  4. 反射限制:默认情况下,非导出包不允许反射访问,需使用opens显式开放

迁移策略

对于现有项目,推荐采用以下迁移路径:

  1. 作为自动模块:先将 jar 放在模块路径而非类路径,成为自动模块
  2. 添加 module-info.java:逐步为每个组件添加模块描述
  3. 拆分模块:根据内聚性原则重构为多个模块
  4. 管理依赖:重新考虑 exports 和 requires 语句,确保最小化暴露

2. JShell - Java 的交互式 REPL 环境

Java 9 引入了令人期待已久的 REPL(Read-Eval-Print Loop)工具,让我们能快速测试 Java 代码片段。

使用案例

启动 JShell 很简单:

$ jshell
|  Welcome to JShell -- Version 9
|  For an introduction type: /help intro

基本代码测试:

jshell> int x = 10
x ==> 10

jshell> int y = 20
y ==> 20

jshell> x + y
$3 ==> 30

jshell> String greeting = "Hello, Java 9!"
greeting ==> "Hello, Java 9!"

jshell> greeting.toUpperCase()
$5 ==> "HELLO, JAVA 9!"

高级用法 - 导入外部类和定义类:

// 导入外部包
jshell> import java.util.concurrent.CompletableFuture

// 定义类
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> Person p = new Person("张三", 30)
p ==> 张三, 30

// 加载外部脚本
jshell> /open MyUtils.java

JShell 关键优势

  1. 快速测试和学习:无需编写完整类就能执行代码
  2. 即时反馈:立即看到执行结果
  3. API 探索:快速尝试和理解 API 功能
  4. 教学工具:特别适合学习和教学环境
  5. 原型设计:快速验证算法或设计想法

局限性

  1. 性能:不适合性能测试,因为 JShell 环境有额外开销
  2. 持久化:会话结束后代码丢失,虽可保存但不如正式项目管理
  3. 调试功能有限:复杂调试场景仍需 IDE

3. 私有接口方法

Java 8 引入了接口中的默认方法和静态方法,Java 9 更进一步,允许在接口中使用私有方法。

使用案例

public interface PaymentProcessor {
    // 公共抽象方法
    void processPayment(double amount);

    // 默认方法
    default void processExpress(double amount) {
        log("开始快速支付处理");
        validateAmount(amount);  // 调用私有方法
        processPayment(amount);
        log("快速支付处理完成");
    }

    default void processStandard(double amount) {
        log("开始标准支付处理");
        validateAmount(amount);  // 复用相同逻辑
        processPayment(amount);
        log("标准支付处理完成");
    }

    // 私有方法 - Java 9新特性
    private void validateAmount(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("支付金额必须大于零");
        }
        if (amount > 100000) {
            log("大额支付警告:" + amount);
        }
    }

    // 私有静态方法 - Java 9新特性
    private static void log(String message) {
        System.out.println("支付日志: " + message);
    }
}

私有接口方法的优势

  1. 代码复用:允许在默认方法之间共享代码,避免重复
  2. 更好的封装:实现细节可以保持私有,不暴露给实现类
  3. 更干净的接口:避免了辅助方法的公开暴露
  4. 减少实现类负担:无需在实现类中提供辅助功能

注意事项

  1. 只能在接口内部调用:私有方法不能被实现类或子接口访问
  2. 静态和非静态区别:私有静态方法可以被其他静态和非静态方法调用,但私有非静态方法不能被静态方法调用

4. 集合工厂方法

Java 9 之前,创建小型不可变集合很繁琐。Java 9 引入了便捷的工厂方法。

使用案例对比

Java 8 方式

// 创建不可变List
List<String> list = Collections.unmodifiableList(Arrays.asList("Java", "Kotlin", "Scala"));

// 创建不可变Set
Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("红", "绿", "蓝")));

// 创建不可变Map
Map<String, Integer> map = Collections.unmodifiableMap(new HashMap<String, Integer>() {{
    put("一", 1);
    put("二", 2);
    put("三", 3);
}});

Java 9 方式

// 创建不可变List
List<String> list = List.of("Java", "Kotlin", "Scala");

// 创建不可变Set
Set<String> set = Set.of("红", "绿", "蓝");

// 创建不可变Map
Map<String, Integer> map = Map.of(
    "一", 1,
    "二", 2,
    "三", 3
);

// 更多键值对使用ofEntries方法
Map<String, Integer> largeMap = Map.ofEntries(
    Map.entry("一", 1),  // Map.entry是静态方法,创建Map.Entry实例
    Map.entry("二", 2),
    Map.entry("三", 3),
    Map.entry("四", 4)
);

工厂方法的优势

  1. 代码简洁:显著减少了创建小型不可变集合的代码量
  2. 不可变保证:返回的集合不允许修改,尝试修改会抛出 UnsupportedOperationException
  3. 空值限制:不允许 null 元素,尝试添加 null 会抛出 NullPointerException
  4. 性能优化:专为小型集合优化,内存占用更少

局限性

  1. 不支持 null 元素:如果尝试创建包含 null 的集合(如 Set.of(null)),会立即抛出 NullPointerException
  2. 元素数量限制:直接的 of 方法最多支持 10 个元素(Map.of 最多支持 5 个键值对)
  3. 不可修改:如果需要后续修改集合,需使用其他构造方法
  4. 元素唯一性:Set.of 和 Map.of 会检查重复,重复元素会导致 IllegalArgumentException

5. Stream API 增强

Java 9 为 Stream API 添加了几个实用的新方法。

新方法使用案例

1. takeWhile() 和 dropWhile() - 短路操作

List<Integer> numbers = List.of(2, 4, 6, 8, 9, 10, 12);

// takeWhile:获取元素,直到条件不满足为止(短路操作)
List<Integer> evensUntilOdd = numbers.stream()
    .takeWhile(n -> n % 2 == 0)
    .collect(Collectors.toList());
System.out.println(evensUntilOdd);  // 输出: [2, 4, 6, 8]

// dropWhile:丢弃元素,直到条件不满足为止(短路操作)
List<Integer> afterFirstOdd = numbers.stream()
    .dropWhile(n -> n % 2 == 0)
    .collect(Collectors.toList());
System.out.println(afterFirstOdd);  // 输出: [9, 10, 12]

// 对比filter(非短路,处理整个流)
List<Integer> allEvens = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());
System.out.println(allEvens);  // 输出: [2, 4, 6, 8, 10, 12]

2. iterate 方法重载 - 有限流生成

// Java 8 的无限流
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 2);
// 需要额外使用limit限制
infiniteStream.limit(10).forEach(System.out::println);

// Java 9 新增的有限流 - 添加了停止条件
Stream<Integer> evenNumbersUntil100 = Stream.iterate(0, n -> n <= 100, n -> n + 2);
evenNumbersUntil100.forEach(System.out::println);  // 打印0到100的偶数,自动停止

3. ofNullable 方法 - 安全处理可能为 null 的值

// 模拟可能返回null的方法
public static String getNameFromDatabase(int id) {
    // 假设id=1时返回名字,其他情况返回null
    return id == 1 ? "张三" : null;
}

// 处理可能为null的值 - Java 8方式
String name = getNameFromDatabase(2);
Stream<String> nameStream = name == null ? Stream.empty() : Stream.of(name);

// Java 9方式 - 更简洁
Stream<String> nameStream = Stream.ofNullable(getNameFromDatabase(2));
// 如果结果为null,返回空流而不是抛出异常

// 在处理可选数据时非常有用
List<String> names = List.of("user1", "user2", "user3");
List<String> validNames = names.stream()
    .map(id -> getNameFromDatabase(Integer.parseInt(id.substring(4))))
    .flatMap(Stream::ofNullable)  // 过滤掉null值
    .collect(Collectors.toList());

Stream 增强优势分析

  1. 更精确的控制:takeWhile 和 dropWhile 提供了更细粒度的流控制,实现短路处理
  2. 简化代码:特别是处理有限序列和可能为 null 的值时,代码更简洁
  3. null 安全:ofNullable 方法避免了 NullPointerException,简化空值处理
  4. 提高可读性:使流处理逻辑更直观,特别是有条件迭代时
  5. 性能优化:短路操作可以减少不必要的处理,提高效率

注意事项

  1. 顺序敏感:takeWhile 和 dropWhile 对元素顺序敏感,对无序流可能产生不可预测结果
  2. 有限流限制:iterate 的有限版本需要注意终止条件的设计,避免无限循环
  3. 语义区别:理解 takeWhile/dropWhile 与 filter 的本质区别(短路 vs 完整处理)

6. Optional 类增强

Optional 类在 Java 9 中获得了三个新方法,使其更加强大和灵活。

新方法使用案例

1. ifPresentOrElse() - 同时处理存在和不存在情况

Optional<String> optional = Optional.of("Java 9");

// 同时处理存在和不存在的情况 - Java 8方式
if (optional.isPresent()) {
    System.out.println("值存在: " + optional.get());
} else {
    System.out.println("值不存在");
}

// Java 9方式 - 更简洁
optional.ifPresentOrElse(
    value -> System.out.println("值存在: " + value),
    () -> System.out.println("值不存在")
);

2. or() - 提供备选 Optional

Optional<String> optional1 = Optional.empty();
Optional<String> optional2 = Optional.of("备选值");

// 如果optional1为空,则使用optional2 - Java 8方式
String result = optional1.isPresent() ? optional1.get() : optional2.orElse("默认值");

// Java 9方式 - 使用备选Optional
String result = optional1.or(() -> optional2).orElse("默认值");
System.out.println(result);  // 输出: 备选值

// 链式处理多个可选数据源
String value = getUserInput()
    .or(() -> getFromCache())
    .or(() -> getFromDatabase())
    .or(() -> getDefaultValue())
    .orElse("最终默认值");

3. stream() - 将 Optional 转换为 Stream

Optional<String> optional = Optional.of("Java 9");

// 将Optional转换为Stream
Stream<String> stream = optional.stream();
stream.forEach(System.out::println);  // 输出: Java 9

// 空Optional转换为空Stream
Optional<String> emptyOptional = Optional.empty();
long count = emptyOptional.stream().count();  // 结果为0

// 在过滤操作中特别有用
List<Optional<String>> listOfOptionals = Arrays.asList(
    Optional.of("Java"),
    Optional.empty(),
    Optional.of("Kotlin")
);

// Java 8方式 - 繁琐
List<String> filteredList = listOfOptionals.stream()
    .filter(Optional::isPresent)
    .map(Optional::get)
    .collect(Collectors.toList());

// Java 9方式 - 简洁
List<String> filteredList = listOfOptionals.stream()
    .flatMap(Optional::stream)  // 只保留非空值
    .collect(Collectors.toList());

System.out.println(filteredList);  // 输出: [Java, Kotlin]

Optional 增强优势分析

  1. 更流畅的 API:新方法提供更连贯的编程体验,减少条件判断
  2. 更好的 Stream 集成:与 Stream API 的无缝集成,简化集合处理
  3. 减少样板代码:特别是在处理多个 Optional 对象时,代码更加简洁
  4. 函数式风格:强化了函数式编程模式,使代码更加声明式
  5. 更优雅的空值处理:提供多种处理缺失值的策略,代码更加健壮

注意事项

  1. 惰性求值:or()方法的 Supplier 参数仅在需要时才会被调用,有助于提高性能
  2. 性能考虑:链式调用过多可能导致性能开销,应适度使用
  3. 空 Optional 处理:理解 stream()方法对空 Optional 的处理(转换为空流)

7. HTTP/2 客户端

Java 9 引入了新的 HTTP 客户端 API,支持 HTTP/2 协议,替代了老旧的 HttpURLConnection。

graph TD
    A[Java应用] --> B[HttpClient]
    B --> C[HTTP/1.1请求]
    B --> D[HTTP/2请求]
    B --> E[WebSocket]
    B --> F[同步API]
    B --> G[异步API]
    G --> H[CompletableFuture]
    H --> I[thenApply]
    I --> J[thenAccept]
    J --> K[exceptionally]

使用案例

同步请求

// 创建HTTP客户端
HttpClient client = HttpClient.newHttpClient();

// 构建GET请求
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.github.com/users/octocat"))
    .header("User-Agent", "Java 9 HttpClient Bot")
    .GET()  // GET是默认方法,可省略
    .build();

// 同步发送请求
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

// 处理响应
System.out.println("状态码: " + response.statusCode());
System.out.println("响应体: " + response.body());

异步请求与错误处理

// 配置客户端连接池和超时
HttpClient client = HttpClient.newBuilder()
    .connectTimeout(Duration.ofSeconds(10))
    .executor(Executors.newFixedThreadPool(5))  // 自定义线程池
    .build();

// 异步请求
CompletableFuture<String> future = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
    .thenApply(HttpResponse::body)
    .thenApply(body -> {
        System.out.println("收到响应,长度: " + body.length());
        return body;
    })
    .exceptionally(ex -> {
        System.err.println("请求失败: " + ex.getMessage());
        return "Error occurred";
    });

// 处理完成或取消
try {
    String result = future.get(5, TimeUnit.SECONDS);  // 设置超时
    System.out.println(result);
} catch (TimeoutException e) {
    future.cancel(true);  // 取消请求
    System.err.println("请求超时,已取消");
} finally {
    // 关闭自定义线程池
    ExecutorService executor = (ExecutorService)client.executor().orElse(null);
    if (executor != null) {
        executor.shutdown();
    }
}

POST 请求示例

// 构建POST请求
HttpRequest postRequest = HttpRequest.newBuilder()
    .uri(URI.create("https://postman-echo.com/post"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString("{\"name\":\"张三\",\"age\":30}"))
    .build();

// 发送请求
HttpResponse<String> response = client.send(postRequest, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());

新 HTTP 客户端的优势

  1. 支持 HTTP/2:更快的响应、头部压缩、服务器推送等特性
  2. 同步和异步 API:灵活适应不同场景,简化异步编程
  3. 流式请求和响应体:高效处理大型数据
  4. WebSocket 支持:内置的 WebSocket 实现
  5. 易用:比 HttpURLConnection 更直观的 API
  6. 请求/响应链接:响应对象包含对应的请求对象

局限性和注意事项

  1. 孵化器模块:在 Java 9 中为孵化器模块(即实验性质的 API,可能在后续版本中变更),需要特殊引入,Java 11 才正式成为标准模块
  2. 性能调优:对于高并发场景,需要适当配置连接池和线程策略
  3. 资源管理:大量异步请求需要注意资源管理和关闭
  4. 迁移成本:从 HttpURLConnection 迁移需要调整代码逻辑

8. 增强的 Try-With-Resources

Java 9 改进了 try-with-resources 语句,使其更加简洁。

使用案例对比

Java 7/8 方式

// 资源需要在try-with-resources语句中声明
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    e.printStackTrace();
}

// 如果资源在外部声明,需要重新声明
BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
try (BufferedReader r = reader) {  // 重复声明
    String line = r.readLine();
    System.out.println(line);
} catch (IOException e) {
    e.printStackTrace();
}

Java 9 方式

// 外部声明的资源可以直接在try中使用
BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
try (reader) {  // 使用已声明的变量
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    e.printStackTrace();
}

// 多个资源也可以简化
Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
try (conn; stmt) {  // 同时管理两个已声明的资源
    ResultSet rs = stmt.executeQuery("SELECT * FROM users");
    while (rs.next()) {
        System.out.println(rs.getString("name"));
    }
} catch (SQLException e) {
    e.printStackTrace();
}

改进的优势

  1. 更简洁的代码:避免了资源变量的重复声明
  2. 提高可读性:特别是当多个资源需要关闭时
  3. 减少错误:减少了复制粘贴错误的可能性
  4. 灵活性:允许在声明和使用资源之间插入其他逻辑

限制条件

  1. final 或 effectively final:使用的变量必须是 final 或 effectively final
  2. AutoCloseable 要求:资源仍然必须实现 AutoCloseable 接口
  3. 变量作用域:使用的变量必须在 try 块结束后仍在作用域内

9. 改进的钻石操作符

Java 9 允许在匿名内部类中使用钻石操作符,这在 Java 8 中是不允许的。

使用案例对比

Java 8 方式

// 必须明确指定泛型类型
Comparator<String> comparator = new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
};

// 在构造泛型实例的匿名子类时也必须重复类型参数
List<Integer> numbers = new ArrayList<Integer>() {
    @Override
    public boolean add(Integer e) {
        System.out.println("添加元素: " + e);
        return super.add(e);
    }
};

Java 9 方式

// 可以使用钻石操作符
Comparator<String> comparator = new Comparator<>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
};

// 匿名子类也可以使用钻石操作符
List<Integer> numbers = new ArrayList<>() {
    @Override
    public boolean add(Integer e) {
        System.out.println("添加元素: " + e);
        return super.add(e);
    }
};

优势分析

  1. 代码简洁:减少类型重复声明,提高可读性
  2. 保持一致性:与局部变量类型推断的风格一致
  3. 减少错误:避免类型不匹配的错误,特别是在复杂泛型类型中
  4. 维护性:修改类型时只需修改一处,降低维护成本

注意事项

  1. 类型推断限制:某些复杂场景下编译器可能无法正确推断类型
  2. 向后兼容性:Java 9 代码在 Java 8 环境中可能无法编译

10. 进程 API 增强

Java 9 改进了对操作系统进程的控制和管理。

使用案例

// 获取当前进程
ProcessHandle current = ProcessHandle.current();
System.out.println("当前进程ID: " + current.pid());

// 获取进程信息
ProcessHandle.Info info = current.info();
System.out.println("命令: " + info.command().orElse("未知"));
System.out.println("启动时间: " + info.startInstant().orElse(null));
System.out.println("用户: " + info.user().orElse("未知"));
System.out.println("CPU使用时间: " + info.totalCpuDuration().orElse(null));

// 列出所有进程
ProcessHandle.allProcesses()
    .filter(ph -> ph.info().command().isPresent())
    .forEach(ph -> System.out.println(ph.pid() + ": " + ph.info().command().get()));

// 获取进程树
current.children().forEach(child ->
    System.out.println("子进程: " + child.pid()));
current.descendants().forEach(desc ->
    System.out.println("所有后代进程: " + desc.pid()));

// 启动新进程并等待终止
ProcessBuilder pb = new ProcessBuilder("notepad.exe");
Process process = pb.start();
ProcessHandle processHandle = process.toHandle();

// 异步监听进程终止
processHandle.onExit().thenAccept(ph ->
    System.out.println("进程 " + ph.pid() + " 已终止,退出值: " +
        process.exitValue()));

// 强制终止进程
boolean terminated = processHandle.destroy();
// 或强制终止
boolean forciblyTerminated = processHandle.destroyForcibly();

进程 API 增强优势

  1. 原生跨平台:无需使用平台特定代码,统一 API 适用所有平台
  2. 进程信息:更丰富的进程元数据,包括启动时间、命令行等
  3. 进程控制:更好的进程生命周期管理,包括优雅终止
  4. 异步通知:进程终止事件的异步处理,避免阻塞
  5. 进程树:轻松访问子进程、后代进程和父进程

局限性

  1. 权限限制:某些操作系统限制可能导致部分信息不可用
  2. 平台差异:虽然 API 统一,但某些功能在不同平台上行为可能不同
  3. 性能开销:频繁查询进程信息可能有性能开销

11. 多分辨率图像 API

Java 9 引入了处理多分辨率图像的 API,特别适合高 DPI 显示器。

使用案例

// 创建多分辨率图像
List<Image> images = new ArrayList<>();
images.add(ImageIO.read(new File("icon-16.png")));  // 16x16
images.add(ImageIO.read(new File("icon-32.png")));  // 32x32
images.add(ImageIO.read(new File("icon-64.png")));  // 64x64

// 创建多分辨率图像
MultiResolutionImage mrImage = new BaseMultiResolutionImage(images.toArray(new Image[0]));

// 获取特定分辨率的变体
Image variant = mrImage.getResolutionVariant(32, 32);  // 获取最适合32x32显示的图像

// 获取所有变体
List<Image> variants = mrImage.getResolutionVariants();

// 假设有图形上下文
Graphics2D g2d = canvas.createGraphics();
// 在图形上下文中使用多分辨率图像 - 系统会自动选择最佳分辨率
g2d.drawImage(mrImage, x, y, null);

// 根据显示设备选择合适的变体
double deviceScale = 2.0;  // 模拟Retina显示器
Image bestVariant = mrImage.getResolutionVariant(
    16 * deviceScale, 16 * deviceScale);  // 系统会选择32x32的变体

多分辨率图像 API 优势

  1. 高 DPI 适配:在不同 DPI 屏幕上优化显示效果,特别是 HiDPI 显示器
  2. 自动选择:系统根据显示需求选择最佳分辨率变体
  3. 兼容性:与现有图像 API 无缝集成
  4. 简化开发:减轻开发者处理多分辨率屏幕的负担
  5. 平台一致性:跨平台提供一致的高质量显示

限制和注意事项

  1. 内存消耗:存储多个分辨率变体需要更多内存
  2. 性能考虑:处理大量多分辨率图像可能影响性能
  3. 变体匹配算法:了解系统如何选择最佳变体,确保提供合适的分辨率集

12. 其他值得关注的改进

1. Reactive Streams API

Java 9 引入了java.util.concurrent.Flow类,提供了响应式编程的标准 API。

// 发布者
class MyPublisher implements Flow.Publisher<String> {
    private List<Flow.Subscriber<? super String>> subscribers = new ArrayList<>();

    @Override
    public void subscribe(Flow.Subscriber<? super String> subscriber) {
        subscribers.add(subscriber);
        subscriber.onSubscribe(new Flow.Subscription() {
            @Override
            public void request(long n) {
                // 发送数据
            }

            @Override
            public void cancel() {
                subscribers.remove(subscriber);
            }
        });
    }

    public void publish(String item) {
        subscribers.forEach(s -> s.onNext(item));
    }
}

// 订阅者
class MySubscriber implements 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("完成");
    }
}

与 Spring Reactor 结合

// 使用Reactor库实现响应式流
import reactor.core.publisher.Flux;

// 创建Flux发布者
Flux<String> messages = Flux.just("消息1", "消息2", "消息3")
    .concatWith(Flux.error(new RuntimeException("模拟错误")))
    .concatWith(Flux.just("消息4"))
    .onErrorReturn("错误后的备用消息");

// 订阅处理
messages.subscribe(
    msg -> System.out.println("接收: " + msg),
    error -> System.err.println("错误: " + error),
    () -> System.out.println("完成")
);

2. CompletableFuture API 改进

Java 9 为 CompletableFuture 添加了超时、延迟和其他增强功能。

// 添加超时
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(2000);  // 模拟长时间运行
        return "结果";
    } catch (InterruptedException e) {
        return "interrupted";
    }
});

// 设置超时 - 1秒后超时并返回默认值
String result = future.completeOnTimeout("超时默认值", 1, TimeUnit.SECONDS)
    .join();
System.out.println(result);  // 输出: 超时默认值

// 延迟执行
CompletableFuture<String> delayed = CompletableFuture
    .supplyAsync(() -> "延迟结果")
    .orTimeout(5, TimeUnit.SECONDS)  // 5秒超时,抛出异常
    .completeOnTimeout("超时值", 3, TimeUnit.SECONDS);  // 3秒后返回默认值

3. Stack-Walking API

新的 Stack-Walking API 提供了更高效的堆栈遍历方式。

// 获取调用栈并过滤
StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);

// 查找特定类的调用者
Optional<Class<?>> callerClass = walker.walk(frames ->
    frames.filter(f -> !f.getClassName().equals(MyClass.class.getName()))
          .findFirst()
          .map(StackWalker.StackFrame::getDeclaringClass));

// 打印整个调用栈
walker.forEach(frame -> System.out.println(frame.getClassName() + "." +
    frame.getMethodName() + " (行: " + frame.getLineNumber() + ")"));

// 收集特定深度的调用栈
List<String> callStack = walker.walk(frames ->
    frames.limit(5)  // 只获取前5个帧
          .map(frame -> frame.getClassName() + "." + frame.getMethodName())
          .collect(Collectors.toList()));

4. 增强的 Deprecation

@Deprecated 注解增加了 forRemoval 和 since 属性。

// Java 8方式
@Deprecated
public void oldMethod() {
    // 实现
}

// Java 9方式 - 更丰富的信息
@Deprecated(since = "9", forRemoval = true)
public void veryOldMethod() {
    // 这个方法将在未来版本中删除
}

@Deprecated(since = "9", forRemoval = false)
public void legacyMethod() {
    // 这个方法不推荐使用,但暂时不会删除
}

总结

以下是 Java 9 主要新特性的总结表格:

特性核心优势应用场景注意事项
模块系统强封装性、明确依赖、运行时优化大型应用、库开发、微服务遗留代码兼容性问题、反射限制
JShell快速测试、即时反馈、原型设计学习、原型开发、API 探索不适用于性能测试、有限的调试
私有接口方法代码复用、更好封装接口设计、API 开发仅接口内部可见、静态方法限制
集合工厂方法代码简洁、不可变保证配置数据、常量集合不支持 null(抛出 NullPointerException)、元素数量限制
Stream API 增强短路操作、简化代码、null 安全数据处理、函数式编程顺序敏感、语义差异
Optional 增强流畅 API、更好集成、函数式风格空值处理、函数式编程惰性求值、性能考虑
HTTP/2 客户端HTTP/2 支持、异步 API、WebSocketWeb 服务集成、API 调用孵化器模块、资源管理
改进的 Try-With-Resources更简洁代码、提高可读性资源管理、IO 操作变量必须为 final 或 effectively final
改进的钻石操作符代码简洁、类型安全泛型编程、集合操作复杂场景推断限制
进程 API 增强原生跨平台、丰富进程信息系统监控、进程管理权限限制、平台差异
多分辨率图像 API高 DPI 适配、自动选择GUI 开发、桌面应用内存消耗、变体选择算法

Java 9 虽然没有 Java 8 那样的语法革命,但它通过模块系统和 API 改进为 Java 生态系统带来了基础架构级别的升级。这些变革为 Java 平台的长期发展奠定了基础,特别是在微服务、容器化和云原生应用开发方面。

模块系统解决了"JAR 地狱"问题,提高了平台安全性,也为 JDK 本身的演进提供了更灵活的路径。这为后续 Java 11、Java 17 等 LTS 版本中的重大改进铺平了道路。模块系统的引入使得 Java 平台能够实现更精细的功能拆分,在 Java 11 中实现了模块化 JRE,大幅减小了运行时的体积,为容器化部署和微服务架构提供了更好的支持。

迁移建议

对于想要利用 Java 9 模块系统的企业开发者,建议采用以下渐进式迁移策略:

  1. 从自动模块开始:首先将现有 JAR 放在模块路径上,让它们成为自动模块,不需要 module-info.java
  2. 增量添加模块描述:为每个组件逐步添加模块描述文件,优先从底层库开始
  3. 重构内部依赖:基于"高内聚、低耦合"原则重新设计模块边界
  4. 利用 JPMS 工具:使用 jdeps 等工具分析依赖关系,帮助确定模块结构
  5. 应用封装特性:逐步应用强封装,利用 exports 和 opens 限制不必要的 API 暴露

这种渐进式迁移可以最小化风险,同时逐步获得模块系统的好处。


感谢您耐心阅读到这里!如果觉得本文对您有帮助,欢迎点赞 👍、收藏 ⭐、分享给需要的朋友,您的支持是我持续输出技术干货的最大动力!

如果想获取更多 Java 技术深度解析,欢迎点击头像关注我,后续会每日更新高质量技术文章,陪您一起进阶成长~


异常君
1 声望1 粉丝

在 Java 的世界里,永远有下一座技术高峰等着你。我愿做你登山路上的同频伙伴,陪你从看懂代码到写出让自己骄傲的代码。咱们,代码里见!