从 Java 8 升级视角看Java 17 新特性详解

大家好~ 我是深耕 Java & 大数据领域的老兵,10 + 年实战经验聚焦分布式架构落地 + 大数据工程化。熟用 Java 生态全栈(SpringBoot/SpringCloud 微服务架构设计、高可用改造),了解 Spark/Flink 大数据计算引擎(离线批处理、实时流计算实战),深谙分布式系统设计原则与性能优化技巧。拆解技术难点、分享实战方案(架构设计 / 踩坑复盘 / 工具选型),关注我,一起从 "会用" 到 "精通",进阶技术管理层~


Java 17 新特性详解

Java 17 是 2021 年发布的长期支持(LTS)版本,也是继 Java 8 后的第三个 LTS 版本,中间跳过了非 LTS 的 9-16。从 Java 8 升级到 17 能获得大量生产力提升特性、性能优化和安全性增强

一、核心正式特性,生产环境可直接使用

1. 模块化系统(Module System)- JEP 261(Java 9 引入,17 完善)

特性概述

解决 Java 8 JAR 地狱问题:将代码按功能拆分为模块,明确依赖关系,控制类的可见性,减少内存占用,提升安全性。

  • Java 8:所有类都在全局命名空间,无法限制内部 API 访问,JRE 体积庞大(包含所有功能);
  • Java 17:模块化后可按需加载,仅暴露需要的 API,JRE 可裁剪为仅包含必要模块。
代码示例(模块化项目结构)

步骤 1:定义模块(module-info.java)

java 复制代码
// src/main/java/module-info.java(模块描述文件,Java 8 无此文件)
module com.example.user {
    // 暴露 com.example.user.api 包给外部模块
    exports com.example.user.api;
    // 依赖 java.base 模块(默认隐式依赖,Java 核心模块)
    requires java.sql;
}

步骤 2:模块内代码

java 复制代码
// 对外暴露的 API 类(com.example.user.api.UserApi.java)
package com.example.user.api;
import com.example.user.internal.UserService; // 内部类

public class UserApi {
    public void sayHello() {
        new UserService().doInternalWork();
    }
}

// 内部类(com.example.user.internal.UserService.java)
package com.example.user.internal;
// 非 exports 包,外部模块无法访问(Java 8 无此限制)
public class UserService {
    public void doInternalWork() {
        System.out.println("执行内部逻辑");
    }
}

步骤 3:编译运行(对比 Java 8)

bash 复制代码
# Java 8 编译(无模块)
javac -d bin src/main/java/com/example/user/**/*.java

# Java 17 编译(模块化)
javac -d bin --module-source-path src/main/java src/main/java/module-info.java src/main/java/com/example/user/**/*.java
关键说明
  • 核心模块:java.base(所有模块默认依赖,包含 String、List 等核心类)、java.sql(数据库)、java.desktop(桌面应用)等;
  • 模块化优势:
    • 减少内存占用(仅加载必要模块);
    • 防止非法访问内部 API(如 sun.misc.Unsafe);
    • 微服务场景下可裁剪 JRE 体积(从几百 MB 降到几十 MB)。

2. Records(记录类)- JEP 395(Java 16 正式,17 保留)

特性概述

简化不可变数据载体类 (如 DTO、VO、POJO)的编写,自动生成 equals()hashCode()toString()、全参构造器和只读访问器,彻底告别样板代码。

  • Java 8:需手动编写所有模板代码,易出错、冗余;
  • Java 17:一行代码定义不可变数据类,代码量减少 80%。
代码示例
java 复制代码
// Java 17:Records 一行定义数据类
public record User(String id, String name, int age) {}

// 测试代码
public class RecordTest {
    public static void main(String[] args) {
        User user = new User("1001", "张三", 25);
        
        // 自动生成的只读访问器(无 set 方法,不可变)
        System.out.println(user.id());   // 输出:1001(Java 8 是 getId())
        System.out.println(user.name()); // 输出:张三
        
        // 自动生成的 toString()
        System.out.println(user); // 输出:User[id=1001, name=张三, age=25]
        
        // 自动生成的 equals()
        User user2 = new User("1001", "张三", 25);
        System.out.println(user.equals(user2)); // 输出:true
    }
}

// Java 8 等效实现(需手动写所有代码)
public final class User8 {
    private final String id;
    private final String name;
    private final int age;

    // 全参构造器
    public User8(String id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    // 只读 getter
    public String getId() { return id; }
    public String getName() { return name; }
    public int getAge() { return age; }

    // equals()
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User8 user8 = (User8) o;
        return age == user8.age && id.equals(user8.id) && name.equals(user8.name);
    }

    // hashCode()
    @Override
    public int hashCode() {
        return java.util.Objects.hash(id, name, age);
    }

    // toString()
    @Override
    public String toString() {
        return "User8{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
关键说明
  • Record 是 final 类,不可继承,默认实现 java.lang.Record

  • 支持自定义校验逻辑(构造器增强):

    java 复制代码
    public record User(String id, String name, int age) {
        // 自定义构造器校验(编译时自动整合到全参构造器)
        public User {
            if (age < 0 || age > 150) {
                throw new IllegalArgumentException("年龄非法:" + age);
            }
            if (id == null || id.isBlank()) {
                throw new IllegalArgumentException("ID 不能为空");
            }
        }
    }

3. Sealed Classes(密封类/接口)- JEP 409(Java 17 正式)

特性概述

限制类的继承或接口的实现范围,只允许指定的类继承/实现,解决 Java 8 继承无限制导致的封装性问题。

  • Java 8:类要么 final(完全禁止继承),要么可被任意子类继承,无中间态;
  • Java 17:通过 sealed 精准控制继承范围,兼顾灵活性和封装性。
代码示例
java 复制代码
// 密封类:仅允许 Student 和 Teacher 继承
public sealed class Person permits Student, Teacher {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
}

// 允许继承的子类(必须用 final/non-sealed/sealed 修饰)
public final class Student extends Person {
    private String studentId;

    public Student(String name, int age, String studentId) {
        super(name, age);
        this.studentId = studentId;
    }
}

// 允许继承的子类(non-sealed 表示该子类可被任意继承)
public non-sealed class Teacher extends Person {
    private String teacherId;

    public Teacher(String name, int age, String teacherId) {
        super(name, age);
        this.teacherId = teacherId;
    }
}

// 错误示例:非 permits 列表中的类无法继承 Person(Java 8 无此限制)
// public class Worker extends Person {} // 编译报错
关键说明
  • 密封接口同理:sealed interface Shape permits Circle, Square {}
  • 适用场景:枚举的扩展(支持多态)、框架的核心类(防止被随意继承破坏逻辑)。

4. Text Blocks(文本块)- JEP 378(Java 15 正式,17 保留)

特性概述

简化多行字符串的编写,无需转义换行、引号,提升 JSON/XML/SQL 等多行文本的可读性。

  • Java 8:需用 + 拼接或 \n 转义,代码杂乱且易出错;
  • Java 17:用 """ 包裹多行文本,自动保留格式,无需转义。
代码示例
java 复制代码
public class TextBlockTest {
    public static void main(String[] args) {
        // Java 17:文本块(JSON 示例)
        String json = """
                {
                    "id": "1001",
                    "name": "张三",
                    "age": 25,
                    "address": "北京市朝阳区"
                }""";
        System.out.println(json);

        // Java 8 等效写法(冗余且易出错)
        String json8 = "{\n" +
                "    \"id\": \"1001\",\n" +
                "    \"name\": \"张三\",\n" +
                "    \"age\": 25,\n" +
                "    \"address\": \"北京市朝阳区\"\n" +
                "}";
        System.out.println(json8);
        
        // 文本块格式化(\s 忽略多余空格,Java 15+ 支持)
        String sql = """
                SELECT id, name, age 
                FROM user 
                WHERE age > \s
                18
                """;
        System.out.println(sql); // 输出:SELECT id, name, age FROM user WHERE age > 18
    }
}
关键说明
  • 文本块以 """ 开头和结尾,开头后必须换行;
  • 自动去除首尾空格,可通过 \s 保留必要空格;
  • 支持所有转义字符(\n\t\" 等),但普通引号无需转义。

5. var 局部变量类型推断 - JEP 286(Java 10 引入)

特性概述

简化局部变量声明,编译器自动推断变量类型,减少冗余代码,同时不丢失类型安全。

  • Java 8:必须显式声明变量类型,代码冗长;
  • Java 17:局部变量可用 var 替代具体类型,代码更简洁。
代码示例
java 复制代码
public class VarTest {
    public static void main(String[] args) {
        // Java 17:var 推断类型
        var list = new ArrayList<String>(); // 推断为 ArrayList<String>
        list.add("Java 17");
        list.add("Java 8");

        var map = new HashMap<Integer, String>(); // 推断为 HashMap<Integer, String>
        map.put(1, "张三");
        map.put(2, "李四");

        // 遍历也可用 var
        for (var entry : map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }

        // Java 8 等效写法(必须显式声明类型)
        ArrayList<String> list8 = new ArrayList<String>();
        HashMap<Integer, String> map8 = new HashMap<Integer, String>();
        for (HashMap.Entry<Integer, String> entry8 : map8.entrySet()) {
            System.out.println(entry8.getKey() + ": " + entry8.getValue());
        }
    }
}
关键说明
  • var 仅适用于局部变量(方法内、循环变量),不能用于类成员、方法参数、返回值;
  • 类型推断在编译期完成,运行时无性能损耗,仍保持静态类型安全;
  • 示例:var num = 10; 推断为 intvar str = "hello" 推断为 String

6. Switch 表达式增强 - JEP 361(Java 14 正式,17 完善)

特性概述

Switch 支持箭头语法、直接返回值、多值匹配,解决 Java 8 Switch break 陷阱和代码冗余问题。

  • Java 8:Switch 仅支持语句,需手动 break,无法直接返回值;
  • Java 17:Switch 可作为表达式返回值,支持 case L1, L2: 多值匹配,代码更简洁。
代码示例
java 复制代码
public class SwitchTest {
    // Java 17:Switch 表达式(返回值)
    public static String getSeason(int month) {
        return switch (month) {
            case 12, 1, 2 -> "冬季"; // 多值匹配 + 箭头语法(无需 break)
            case 3, 4, 5 -> "春季";
            case 6, 7, 8 -> "夏季";
            case 9, 10, 11 -> "秋季";
            default -> throw new IllegalArgumentException("无效月份:" + month);
        };
    }

    // Java 8:Switch 语句(需 break,无法直接返回)
    public static String getSeason8(int month) {
        String season;
        switch (month) {
            case 12:
            case 1:
            case 2:
                season = "冬季";
                break;
            case 3:
            case 4:
            case 5:
                season = "春季";
                break;
            case 6:
            case 7:
            case 8:
                season = "夏季";
                break;
            case 9:
            case 10:
            case 11:
                season = "秋季";
                break;
            default:
                throw new IllegalArgumentException("无效月份:" + month);
        }
        return season;
    }

    public static void main(String[] args) {
        System.out.println(getSeason(3)); // 输出:春季
        System.out.println(getSeason8(3)); // 输出:春季
    }
}

7. Stream API 增强(Java 9+)

特性概述

Java 8 的 Stream API 新增 takeWhile()dropWhile()ofNullable()toList() 等方法,简化流操作。

代码示例
java 复制代码
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> list = List.of(1, 2, 3, 4, 5, 4, 3, 2, 1);

        // 1. takeWhile:取满足条件的前缀(直到第一个不满足条件的元素)
        // Java 17
        var takeWhile = list.stream().takeWhile(n -> n < 4).collect(Collectors.toList());
        System.out.println(takeWhile); // 输出:[1,2,3]
        
        // Java 8 等效(需手动过滤,逻辑复杂)
        var takeWhile8 = list.stream()
                .filter(n -> {
                    boolean[] flag = {true};
                    flag[0] = n < 4;
                    return flag[0];
                })
                .collect(Collectors.toList()); // 实际需更复杂的逻辑
        
        // 2. dropWhile:丢弃满足条件的前缀(直到第一个不满足条件的元素)
        var dropWhile = list.stream().dropWhile(n -> n < 4).collect(Collectors.toList());
        System.out.println(dropWhile); // 输出:[4,5,4,3,2,1]
        
        // 3. ofNullable:避免空指针(Java 8 无此方法)
        Stream<String> stream = Stream.ofNullable(null); // 空流,不会 NPE
        System.out.println(stream.count()); // 输出:0
        
        // 4. toList():简化收集(Java 16+,替代 Collectors.toList())
        var newList = list.stream().filter(n -> n % 2 == 0).toList();
        System.out.println(newList); // 输出:[2,4,4,2]
    }
}

8. Optional 增强(Java 9+)

特性概述

Java 8 的 Optional 新增 orElseThrow()stream()ifPresentOrElse() 等方法,简化空值处理。

代码示例
java 复制代码
import java.util.Optional;

public class OptionalTest {
    public static void main(String[] args) {
        Optional<String> optional = Optional.ofNullable("Java 17");
        Optional<String> emptyOptional = Optional.empty();

        // 1. orElseThrow():无值时抛异常(Java 8 需手动写,Java 10 简化)
        String value = optional.orElseThrow(); // 有值返回 "Java 17"
        // emptyOptional.orElseThrow(); // 无值抛 NoSuchElementException
        
        // Java 8 等效
        String value8 = optional.orElseThrow(() -> new java.util.NoSuchElementException("No value present"));

        // 2. ifPresentOrElse:有值执行消费逻辑,无值执行默认逻辑
        emptyOptional.ifPresentOrElse(
                s -> System.out.println("值:" + s),
                () -> System.out.println("无值") // 输出:无值
        );

        // 3. stream():将 Optional 转为 Stream(Java 9+)
        optional.stream().forEach(s -> System.out.println(s)); // 输出:Java 17
    }
}

9. UTF-8 by Default(默认 UTF-8 编码)- JEP 400(Java 17 正式)

特性概述

JDK 所有 API 的默认字符编码改为 UTF-8,解决 Java 8 跨平台编码不一致问题(Windows 默认为 GBK、Linux/Mac 默认为 UTF-8)。

  • Java 8:读取/写入文件、网络传输时需手动指定 UTF-8,否则易出现中文乱码;
  • Java 17:统一为 UTF-8,跨平台编码一致,无需手动指定。
代码示例
java 复制代码
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Utf8Test {
    public static void main(String[] args) throws IOException {
        String content = "Java 17 新特性:中文测试";

        // Java 17:默认 UTF-8 写入文件
        try (FileWriter writer = new FileWriter("test.txt")) {
            writer.write(content);
        }

        // 读取文件(默认 UTF-8,无乱码)
        String readContent = Files.readString(Paths.get("test.txt"));
        System.out.println(readContent); // 输出:Java 17 新特性:中文测试
        
        // Java 8 需手动指定编码(否则 Windows 下乱码)
        // Files.readString(Paths.get("test.txt"), java.nio.charset.StandardCharsets.UTF_8);
    }
}

二、性能与 GC 增强(无代码但收益显著)

1. ZGC(Z Garbage Collector)- JEP 391(Java 17 正式)

  • Java 8:默认 GC 是 G1,大内存应用(如 32GB+)停顿时间可达数百毫秒;
  • Java 17:ZGC 正式转正,停顿时间 < 1ms,支持最大 16TB 堆内存,适合高并发、低延迟场景(如微服务、大数据)。

启用 ZGC(Java 17)

bash 复制代码
java -XX:+UseZGC -Xmx16g YourApp.java

2. Shenandoah GC - JEP 412(Java 17 正式)

并行垃圾回收器,低延迟(停顿时间 < 10ms),适合需要快速响应的应用(如电商、金融)。

启用 Shenandoah GC

bash 复制代码
java -XX:+UseShenandoahGC YourApp.java

3. 其他性能优化

  • 字符串压缩:Java 17 优化了 String 存储,减少内存占用;
  • 即时编译(JIT)优化:C2 编译器改进,热点代码执行速度提升;
  • 模块化裁剪:JRE 体积可缩小至原来的 1/10,启动速度更快。

三、预览特性(需手动开启,生产谨慎使用)

1. Pattern Matching for switch(switch 模式匹配)- JEP 406

特性概述

switch 支持类型匹配,无需手动类型转换,简化多类型判断逻辑(Java 21 正式转正)。

代码示例(需添加编译参数:--enable-preview --release 17
java 复制代码
public class SwitchPatternTest {
    public static String getType(Object obj) {
        // Java 17:switch 类型匹配 + 自动转换
        return switch (obj) {
            case Integer i -> "整数:" + i;
            case String s -> "字符串:" + s.length();
            case Double d when d > 0 -> "正数浮点数:" + d;
            case null -> "空值";
            default -> "未知类型:" + obj.getClass().getName();
        };
    }

    // Java 8 等效写法(嵌套 if-else + 强转)
    public static String getType8(Object obj) {
        if (obj instanceof Integer) {
            Integer i = (Integer) obj;
            return "整数:" + i;
        } else if (obj instanceof String) {
            String s = (String) obj;
            return "字符串:" + s.length();
        } else if (obj instanceof Double) {
            Double d = (Double) obj;
            if (d > 0) {
                return "正数浮点数:" + d;
            }
        } else if (obj == null) {
            return "空值";
        }
        return "未知类型:" + obj.getClass().getName();
    }

    public static void main(String[] args) {
        System.out.println(getType(100));    // 输出:整数:100
        System.out.println(getType("Java17"));// 输出:字符串:6
    }
}

四、从 Java 8 升级到 17 的注意事项

1. 兼容性问题(核心)

  • 废弃 API :Java 8 的 Thread.stop()Date 部分方法、Applet API 等被废弃,需替换为标准 API;
  • 模块化限制 :无法直接访问 JDK 内部 API(如 sun.misc.Unsafe),需替换为 VarHandle 等标准 API;
  • 反射变化 :模块化后,反射访问非导出包的类会抛出 IllegalAccessException,需调整模块导出。

2. 升级步骤(建议)

  1. 第一步 :将 JDK 版本切换为 17,编译参数添加 -source 8 -target 17(兼容 Java 8 代码);
  2. 第二步:逐步替换废弃 API,使用新特性重构核心代码(如用 Records 替换 DTO);
  3. 第三步:可选引入模块化,拆分大型项目为多个模块;
  4. 第四步:测试环境启用 ZGC/Shenandoah GC,验证性能提升。

3. 工具支持

  • Maven/Gradle:升级插件版本(Maven 3.8+、Gradle 7.0+ 完美支持 Java 17);

  • IDE:IntelliJ IDEA 2021.2+、Eclipse 2021-09+ 支持 Java 17;

  • 构建参数:Maven 需在 pom.xml 中指定:

    xml 复制代码
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>

总结

从 Java 8 升级到 17 是高收益、低风险的选择:

  • 开发效率:Records、Text Blocks、var、Switch 表达式等特性减少 50% 以上样板代码;
  • 性能:ZGC/Shenandoah GC 大幅降低停顿时间,模块化减少内存占用;
  • 安全性:密封类、模块化、默认 UTF-8 提升代码安全性和可维护性;
  • 长期支持:Oracle 提供至 2029 年的安全更新(Java 8 仅支持到 2030 年,但商业支持已停止)。

建议优先使用正式特性(Records、文本块、Switch 增强等),预览特性可在测试环境尝试,待转正后再投入生产。

相关推荐
刘立军7 分钟前
本地大模型编程实战(39)MCP实战演练
人工智能·后端·mcp
xjz18428 分钟前
JVM虚拟线程:JEP 444开启Java并发编程新纪元
java
JH30738 分钟前
Spring Retry 实战:优雅搞定重试需求
java·后端·spring
ZoeGranger10 分钟前
【Spring】使用注解开发
后端
哔哩哔哩技术10 分钟前
2025年哔哩哔哩技术精选技术干货
前端·后端·架构
IT_陈寒19 分钟前
Redis性能翻倍的5个关键策略:从慢查询到百万QPS的实战优化
前端·人工智能·后端
蓝眸少年CY23 分钟前
测试Java性能
java·开发语言·python
何包蛋H23 分钟前
数据结构深度解析:Java Map 家族完全指南
java·开发语言·数据结构
linsa_pursuer43 分钟前
最长连续序列
java·数据结构·算法·leetcode
强子感冒了44 分钟前
Java集合框架深度学习:从Iterable到ArrayList的完整继承体系
java·笔记·学习