Skip to content
<

Java 17 是目前 Java 最主流的 LTS 版本,比例已经超越了 Ja⁢va 8!现在很多新的 Java 开发框架和类库支持⁡的最低 JDK 版本就是 17(比如 AI 开发框架⁡ LangChain4j)。

正式特性

【实用】Sealed 密封类

在很多 Java 开发者的印象⁢中,一个类要么完全开⁡放继承(任何类都能继⁡承),要么完全禁止继承(final 类)。

java
// 选择1:完全开放继承  
public class Shape {  
    // 问题:不知道会有哪些子类,难以进行穷举  
}  
  
// 选择2:完全禁止继承  
public final class Circle {  
    // 问题:即使在同一个模块内也无法继承  
}

其实这样是没办法精确控制继承⁢关系的,在设计 A⁡PI 或领域模型时⁡可能会遇到问题。

Java 17 将 Seal⁢ed 密封类转正,⁡让类的继承关系变得⁡更可控和安全。

比如我可以只允许某几个类继承:

java
public sealed class Shape   
    permits Circle, Rectangle, Triangle {  
    // 只允许这三个类继承  
}

但是,被允许继承的子类必须选择一种继承策略:

1)final:到我为止,不能再继承了

java
public final class Circle extends Shape {  
}

2)sealed:我也要控制谁能继承我

java
public sealed class Triangle extends Shape   
    permits RightTriangle {  
}

3)non-sealed:我开放继承,任何人都可以继承我

java
public non-sealed class Rectangle extends Shape {  
}

强制声明继承策略是为了 确保设计控制权的完整传递。如果不强制声明,sealed 类精确控制继承的价值就会被破坏,任何人都可以通过继承子类来绕过原始设计的限制。

注意,虽然看起来 non-seale⁢d 打破了这个设计,但这⁡也是设计者的主动选择。如⁡果不需要强制声明,设计者可能会无意中失去控制权。

有了 Sealed 类后,某⁢个接口可能的实现类⁡型就尽在掌握了,可⁡以让 switch 模式匹配变得更加安全:

java
// 编译器知道所有可能的子类型,可以进行完整性检查  
public double calculateArea(Shape shape) {  
    return switch (shape) {  
        case Circle c -> Math.PI * c.getRadius() * c.getRadius();  
        case Rectangle r -> r.getWidth() * r.getHeight();  
        case Triangle t -> 0.5 * t.getBase() * t.getHeight();  
        // 编译器确保我们处理了所有情况,无需 default 分支  
    };  
}

密封类的实际应用

java
// 定义 API 响应的类型层次  
public sealed interface ApiResponse<T>   
    permits SuccessResponse, ErrorResponse {  
}  
  
public record SuccessResponse<T>(T data, String message) implements ApiResponse<T> {}  
public record ErrorResponse<T>(int errorCode, String errorMessage) implements ApiResponse<T> {}  
  
// 处理响应  
public <T> void handleResponse(ApiResponse<T> response) {  
    switch (response) {  
        case SuccessResponse<T>(var data, var message) -> {  
            System.out.println("成功: " + message);  
            processData(data);  
        }  
        case ErrorResponse<T>(var code, var error) -> {  
            System.err.println("错误 " + code + ": " + error);  
        }  
        // 不需要 default,编译器确保完整性  
    }  
}

【了解】增强的伪随机数生成器

Java 17 引入了全新的随机数生成器⁢ API,提供了更优的性能⁡和更多的算法选择:    ⁡

java
// 传统的随机数  
Random oldRandom = new Random();  
int oldValue = oldRandom.nextInt(100);  
  
// 新的随机数生成器  
RandomGenerator generator = RandomGenerator.of("L32X64MixRandom");  
int newValue = generator.nextInt(100);

新的随机数生成器特性

java
import java.util.random.RandomGenerator;  
import java.util.random.RandomGeneratorFactory;  
  
public class RandomExample {  
    public static void main(String[] args) {  
        // 列出所有可用的算法  
        RandomGeneratorFactory.all()  
            .map(RandomGeneratorFactory::name)  
            .sorted()  
            .forEach(System.out::println);  
          
        // 使用不同的算法  
        RandomGenerator xoroshiro = RandomGenerator.of("Xoroshiro128PlusPlus");  
        RandomGenerator l32x64 = RandomGenerator.of("L32X64MixRandom");  
          
        // 生成随机数  
        System.out.println("Xoroshiro: " + xoroshiro.nextInt(1, 101));  
        System.out.println("L32X64: " + l32x64.nextInt(1, 101));  
          
        // 生成随机流  
        xoroshiro.ints(10, 1, 101)  
                 .forEach(System.out::println);  
          
        // 跳跃功能(某些算法支持)  
        RandomGenerator splittable = RandomGenerator.of("L64X128MixRandom");  
        if (splittable instanceof RandomGenerator.SplittableGenerator split) {  
            RandomGenerator newGen = split.split();  
            System.out.println("分割后的生成器: " + newGen.nextInt(100));  
        }  
    }  
}

新 API 的优势:

  • 更多算法:支持多种高质量的 PRNG 算法

  • 更好性能:针对现代 CPU 优化

  • 功能丰富:支持跳跃、分割等高级功能

  • 统一接口:所有算法使用相同的 API

【了解】强封装 JDK 内部 API

Java 17 进一步强化了对⁢ JDK 内部 AP⁡I 的封装,一些之前⁡可以通过反射访问的内部类现在完全不可访问,比如:

  • sun.misc.Unsafe

  • com.sun.* 包下的类

  • jdk.internal.* 包下的类

虽然这提高了 JDK 的安全⁢性和稳定性,但可能⁡需要迁移一些依赖内⁡部 API 的老代码。

java
// 这些内部 API 在 Java 17 中被强封装  
// sun.misc.Unsafe unsafe = sun.misc.Unsafe.getUnsafe(); // 不再可用  
  
// 如果确实需要(不推荐),可以通过 JVM 参数开放  
// java --add-opens java.base/sun.misc=ALL-UNNAMED MyApp

迁移指南

java
// 替代 sun.misc.Unsafe 的现代方案  
import java.lang.invoke.MethodHandles;  
import java.lang.invoke.VarHandle;  
  
public class ModernUnsafeAlternative {  
    private static final VarHandle ARRAY_HANDLE = MethodHandles.arrayElementVarHandle(int[].class);  
      
    public static void main(String[] args) {  
        int[] array = new int[10];  
          
        // 使用 VarHandle 进行原子操作  
        ARRAY_HANDLE.setVolatile(array, 0, 42);  
        int value = (int) ARRAY_HANDLE.getVolatile(array, 0);  
          
        // CAS 操作  
        boolean success = ARRAY_HANDLE.compareAndSet(array, 0, 42, 100);  
        System.out.println("CAS 成功: " + success);  
    }  
}

【了解】其他正式特性

恢复始终严格的浮点语义

Java 17 恢复了严格的⁢浮点计算语义,确保⁡跨平台的一致性:

java
// 浮点计算现在在所有平台上都是一致的  
public class StrictFloatExample {  
    public static void main(String[] args) {  
        double a = 0.1;  
        double b = 0.2;  
        double c = a + b;  
          
        // 现在在所有平台上结果都一致  
        System.out.println(c); // 0.30000000000000004  
          
        // 使用 BigDecimal 进行精确计算  
        BigDecimal bd1 = new BigDecimal("0.1");  
        BigDecimal bd2 = new BigDecimal("0.2");  
        BigDecimal bd3 = bd1.add(bd2);  
        System.out.println(bd3); // 0.3  
    }  
}

新的 macOS 渲染管道

Java 17 在 macO⁢S 上使用新的渲染⁡管道,提供更好的性⁡能和兼容性:

bash
# 默认使用新的渲染管道  
java -Dsun.java2d.metal=true MySwingApp  
  
# 如果遇到问题,可以回退到旧管道  
java -Dsun.java2d.metal=false MySwingApp

macOS/AArch64 端口

Java 17 正式支持 A⁢pple Sili⁡con(M1/M2⁡)芯片:

bash
# 在 Apple Silicon Mac 上运行  
java -version  
# 输出会显示 aarch64 架构  
  
# 性能通常比 x86_64 模拟更好  
java -XX:+PrintGCDetails MyApp

弃用 Applet API

Java 17 将 Appl⁢et API 标记⁡为弃用并计划删除:

java
// 这些 API 已被弃用  
// public class MyApplet extends Applet { }  
// public class MyJApplet extends JApplet { }  
  
// 推荐使用现代的 Web 技术替代  
// - JavaScript + HTML5  
// - WebAssembly  
// - 桌面应用程序

预览特性

【了解】switch 的模式匹配(预览)

Java 17 引入了 sw⁢itch 的模式匹⁡配作为预览特性:

java
// 需要编译器预览模式  
// javac --enable-preview --release 17 SwitchPatternExample.java  
// java --enable-preview SwitchPatternExample  
  
public String processValue(Object value) {  
    return switch (value) {  
        case String s -> "字符串: " + s;  
        case Integer i -> "整数: " + i;  
        case Long l -> "长整数: " + l;  
        case Double d -> "浮点数: " + d;  
        case null -> "空值";  
        default -> "未知类型: " + value.getClass().getSimpleName();  
    };  
}  
  
// 结合条件判断  
public String processString(Object obj) {  
    return switch (obj) {  
        case String s && s.length() > 10 -> "长字符串: " + s;  
        case String s && s.isEmpty() -> "空字符串";  
        case String s -> "普通字符串: " + s;  
        case null -> "空值";  
        default -> "非字符串";  
    };  
}

与密封类结合

java
public sealed interface Expression   
    permits Constant, Addition, Multiplication {  
}  
  
public record Constant(int value) implements Expression {}  
public record Addition(Expression left, Expression right) implements Expression {}  
public record Multiplication(Expression left, Expression right) implements Expression {}  
  
// 使用模式匹配计算表达式  
public int evaluate(Expression expr) {  
    return switch (expr) {  
        case Constant(var value) -> value;  
        case Addition(var left, var right) -> evaluate(left) + evaluate(right);  
        case Multiplication(var left, var right) -> evaluate(left) * evaluate(right);  
        // 不需要 default,因为密封类保证了完整性  
    };  
}

孵化器特性

【了解】外部函数和内存 API(孵化器)

Java 17 继续完善外部函数和内存 API:

java
import jdk.incubator.foreign.*;  
  
public class ForeignAPIExample {  
    public static void main(String[] args) throws Throwable {  
        // 查找 C 标准库函数  
        SymbolLookup stdlib = CLinker.systemLookup();  
          
        // 查找 printf 函数  
        MemoryAddress printfAddr = stdlib.lookup("printf").orElseThrow();  
          
        // 创建函数描述符  
        FunctionDescriptor printfDesc = FunctionDescriptor.of(  
            CLinker.C_INT,      // 返回类型  
            CLinker.C_POINTER   // 参数类型(格式字符串)  
        );  
          
        // 创建方法句柄  
        MethodHandle printf = CLinker.getInstance()  
            .downcallHandle(printfAddr, printfDesc);  
          
        // 调用 printf  
        try (ResourceScope scope = ResourceScope.newConfinedScope()) {  
            MemorySegment formatStr = CLinker.toCString("Hello from Java %d\n", scope);  
            printf.invoke(formatStr, 2024);  
        }  
    }  
}

【了解】向量 API(第二次孵化器)

Java 17 继续改进向量 API:

java
import jdk.incubator.vector.*;  
  
public class VectorMath {  
    public static void vectorAdd(float[] a, float[] b, float[] result) {  
        VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;  
        int upperBound = SPECIES.loopBound(a.length);  
          
        // 向量化循环  
        for (int i = 0; i < upperBound; i += SPECIES.length()) {  
            FloatVector va = FloatVector.fromArray(SPECIES, a, i);  
            FloatVector vb = FloatVector.fromArray(SPECIES, b, i);  
            FloatVector vc = va.add(vb);  
            vc.intoArray(result, i);  
        }  
          
        // 处理剩余元素  
        for (int i = upperBound; i < a.length; i++) {  
            result[i] = a[i] + b[i];  
        }  
    }  
      
    public static void main(String[] args) {  
        float[] a = {1, 2, 3, 4, 5, 6, 7, 8};  
        float[] b = {8, 7, 6, 5, 4, 3, 2, 1};  
        float[] result = new float[8];  
          
        vectorAdd(a, b, result);  
        System.out.println(Arrays.toString(result));  
        // [9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0]  
    }  
}

性能和工具改进

【了解】上下文特定的反序列化过滤器

Java 17 增强了反序列⁢化安全性:

java
import java.io.*;  
import java.util.function.BinaryOperator;  
  
public class DeserializationFilterExample {  
    public static void main(String[] args) {  
        // 设置全局反序列化过滤器  
        ObjectInputFilter globalFilter = ObjectInputFilter.Config.createFilter(  
            "java.lang.String;java.lang.Number;!*"  // 只允许 String 和 Number  
        );  
        ObjectInputFilter.Config.setSerialFilter(globalFilter);  
          
        // 或者为特定的流设置过滤器  
        try (ObjectInputStream ois = new ObjectInputStream(inputStream)) {  
            ObjectInputFilter contextFilter = info -> {  
                Class<?> clazz = info.serialClass();  
                if (clazz != null) {  
                    // 只允许特定的类  
                    if (clazz == String.class || Number.class.isAssignableFrom(clazz)) {  
                        return ObjectInputFilter.Status.ALLOWED;  
                    }  
                    return ObjectInputFilter.Status.REJECTED;  
                }  
                return ObjectInputFilter.Status.UNDECIDED;  
            };  
              
            ois.setObjectInputFilter(contextFilter);  
            Object obj = ois.readObject();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}

【了解】JVM 改进

Java 17 包含了多项 JVM 改进:

并发标记清除收集器完全移除

bash
# CMS 收集器在 Java 17 中完全不可用  
# java -XX:+UseConcMarkSweepGC MyApp  # 会报错  
  
# 推荐使用的收集器  
java -XX:+UseG1GC MyApp           # G1  
java -XX:+UseZGC MyApp             # ZGC  
java -XX:+UseShenandoahGC MyApp    # Shenandoah

实验性 AOT 和 JIT 编译器移除

bash
# 这些实验性编译器已被移除  
# java -XX:+UseAOTCompilation MyApp      # 不再可用  
# java -XX:+EnableJVMCI MyApp            # 不再可用

总结

Java 17 作为新的 LTS 版本,在稳定⁢性和安全性方面做了大量改进。S⁡ealed 类的正式化为类型安⁡全提供了新的工具,增强的随机数生成器提供了更好的性能和功能。

强封装 JDK 内部 API 虽然可能带来一些迁移成⁢本,但长期来看有利于 Java 生⁡态的健康发展。switch 的模式⁡匹配作为预览特性展现了巨大潜力,为未来的函数式编程奠定了基础。

Java 17 已经成为企业级⁢应用的首选版本,其稳⁡定性和新特性的平衡使⁡其成为从 Java 8/11 升级的理想选择。