从 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 增强等),预览特性可在测试环境尝试,待转正后再投入生产。

相关推荐
淡定__0092 小时前
深入理解 .NET 中的依赖注入(DI):从原理到实战
后端
张人大 Renda Zhang2 小时前
2025 年版笔记:Java 开发如何用 AI 升级 CI/CD 和运维?
java·运维·ci/cd·ai·云原生·架构·自动化
Ankkaya2 小时前
小白服务器踩坑(2)- 自动化部署
后端
回家路上绕了弯2 小时前
Vavr 工具实用指南:Java 函数式编程的高效落地方案
分布式·后端
开心就好20252 小时前
没有 Mac 怎么上架 iOS 应用 跨平台团队的可行交付方案分析
后端
阿里云云原生2 小时前
AgentScope Java v1.0 发布,让 Java 开发者轻松构建企业级 Agentic 应用
java
aiopencode2 小时前
构建可靠的 iOS 日志导出体系,从真机日志到系统行为的多工具协同实践
后端
Swizard2 小时前
极限瘦身:将 Python AI 应用从 100MB 砍到 30MB
java·python·ai·移动开发
zhouyunjian2 小时前
11、一文详解CompletableFuture:来源、定义、方法、与场景使用分析
java·网络·spring boot