大家好~ 我是深耕 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; -
支持自定义校验逻辑(构造器增强):
javapublic 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;推断为int,var 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. 升级步骤(建议)
- 第一步 :将 JDK 版本切换为 17,编译参数添加
-source 8 -target 17(兼容 Java 8 代码); - 第二步:逐步替换废弃 API,使用新特性重构核心代码(如用 Records 替换 DTO);
- 第三步:可选引入模块化,拆分大型项目为多个模块;
- 第四步:测试环境启用 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 增强等),预览特性可在测试环境尝试,待转正后再投入生产。