Java17 出现的各种新特性

先介绍一下 Java17 都出现了哪些新特性

密封类

密封类(Sealed Classes) 密封类允许你控制哪些类可以继承或实现某个类或接口,提供了比访问修饰符更精细的继承控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
public sealed class Shape permits Circle, Rectangle, Triangle {
// 基类实现
}

public final class Circle extends Shape {
private final double radius;
// 实现细节
}

public final class Rectangle extends Shape {
private final double width, height;
// 实现细节
}

模式匹配增强

在instanceof中使用模式匹配,减少了类型转换的样板代码:

1
2
3
4
5
6
7
8
9
10
// Java 17之前
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}

// Java 17
if (obj instanceof String s) {
System.out.println(s.length());
}

Switch 表达式的进一步完善

对 Switch 表达式的进一步完善 支持更复杂的模式匹配和表达式:

1
2
3
4
5
6
String result = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> "6";
case TUESDAY -> "7";
case THURSDAY, SATURDAY -> "8";
case WEDNESDAY -> "9";
};

文本块

文本块(Text Blocks) 多行字符串处理变得更加优雅

1
2
3
4
5
6
7
String json = """
{
"name": "张三",
"age": 30,
"city": "北京"
}
""";

Records类

提供了一种简洁的方式来创建不可变数据载体,类似于 Lombok:

1
2
3
4
5
6
7
public record Person(String name, int age, String email) {
// 自动生成构造器、getter、equals、hashCode、toString
}

// 使用
Person person = new Person("李四", 25, "lisi@example.com");
System.out.println(person.name()); // 李四

性能和垃圾收集改进

ZGC垃圾收集器增强 ZGC在Java 17中得到了显著改进,提供了更低的延迟和更好的性能表现。

G1垃圾收集器优化 并行性能得到提升,减少了停顿时间。

Java17 新特性之 record 记录类

概述

其实 record 记录类是 Java14 就引入的预览特性了,就打算用了,Java16正式发布,并且 Java17 确认为长期支持的版本。record 记录类是Java语言的一个重大特性。它提供了一种简洁的方式来创建不可变的数据载体类。

Record是一种特殊的类,专门用于存储数据。它自动生成构造器、访问器方法、equals()hashCode()toString() 方法,大大减少了样板代码。类似于 Lombok 中的各种注解

  • record为定义class提供了一种紧凑的语法,被record定义的类是浅不可变数据的透明持有者。
  • 浅不可变数据是指这样的一种类,它们用来描述一种简单的数据结构,这种数据结构的属性都是final不可变的(即状态不可变)。
  • 因为final字段只是不能对变量重复赋值,变量引用的对象本身是有可能可以被修改的,比如这个状态是一个集合的话,向集合中添加/删除元素等操作是可以的。

什么是”浅不可变”

“浅不可变”意味着Record的字段引用本身不能改变,但字段所引用的对象内容可能仍然可以被修改。

浅不可变指的是:

  • Record的字段引用是final的,不能重新赋值
  • 但字段所引用的对象内容可能仍然可以被修改,也就是说,若字段引用的对象是集合,那么内容是可以改变的

这就像一个”只读的指针”,指针本身不能改变指向,但指向的内容可能是可变的。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 这个Record看起来"不可变",但实际上有问题
public record ShoppingCart(String userId, List<String> items) {}

ShoppingCart cart = new ShoppingCart("user123", new ArrayList<>());

// ❌ 这样做不行 - 字段是final的
// cart.items = new ArrayList<>();

// ✅ 但这样做可以 - 修改集合内容
cart.items().add("苹果");
cart.items().add("香蕉");

// 结果:Record的"状态"实际上被改变了!

为什么会这样?

Java的final关键字只保证:

  • 基本类型:值不能改变(真正不可变)
  • 引用类型:引用不能改变(但引用的对象内容可能可变)
1
2
3
final List<String> list = new ArrayList<>();
// list = new ArrayList<>(); // ❌ 不能重新赋值
list.add("item"); // ✅ 但可以修改内容

一个完整的 Record 类关于浅不可变特性的例子如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import java.util.*;
import java.time.LocalDate;

// 1. 基本数据类型 - 完全不可变
public record PersonBasic(String name, int age) {}

// 2. 浅不可变的问题示例
public record StudentWithGrades(String name, List<Integer> grades) {}

// 3. 更复杂的浅不可变示例
public record Company(String name, List<String> employees, Map<String, Integer> salaries) {}

// 4. 嵌套对象的浅不可变
public record Address(String street, String city) {}
public record PersonWithAddress(String name, Address address, List<String> hobbies) {}

public class RecordImmutabilityDemo {

public static void demonstrateShallowImmutability() {
System.out.println("=== Record浅不可变特性演示 ===\n");

// 1. 基本类型 - 完全不可变
demonstrateBasicImmutability();

// 2. 集合类型 - 浅不可变问题
demonstrateCollectionMutability();

// 3. 嵌套对象 - 浅不可变问题
demonstrateNestedObjectMutability();

// 4. 解决方案
demonstrateSolutions();
}

private static void demonstrateBasicImmutability() {
System.out.println("1. 基本数据类型的不可变性:");
PersonBasic person = new PersonBasic("张三", 25);

System.out.println("创建person: " + person);

// 以下操作是不可能的,会编译错误:
// person.name = "李四"; // 编译错误!字段是final的
// person.age = 30; // 编译错误!字段是final的

// 只能通过创建新实例来"修改"
PersonBasic newPerson = new PersonBasic("李四", 30);
System.out.println("新person: " + newPerson);
System.out.println("原person不变: " + person);
System.out.println();
}

private static void demonstrateCollectionMutability() {
System.out.println("2. 集合字段的浅不可变问题:");

List<Integer> grades = new ArrayList<>();
grades.add(85);
grades.add(92);
grades.add(78);

StudentWithGrades student = new StudentWithGrades("王五", grades);
System.out.println("创建student: " + student);

// 字段引用不能改变(这会编译错误):
// student.grades = new ArrayList<>(); // 编译错误!

// 但是可以修改集合的内容!这就是"浅不可变"的问题
student.grades().add(95); // 添加新成绩
student.grades().set(0, 90); // 修改第一个成绩

System.out.println("修改后的student: " + student);
System.out.println("注意:grades集合的内容被修改了,但引用没变!");
System.out.println();
}

private static void demonstrateNestedObjectMutability() {
System.out.println("3. 嵌套对象的浅不可变:");

Address address = new Address("中山路123号", "北京");
List<String> hobbies = new ArrayList<>(Arrays.asList("读书", "游泳"));

PersonWithAddress person = new PersonWithAddress("赵六", address, hobbies);
System.out.println("创建person: " + person);

// 不能改变字段引用:
// person.address = new Address("新地址", "上海"); // 编译错误!
// person.hobbies = new ArrayList<>(); // 编译错误!

// 但可以修改集合内容:
person.hobbies().add("跑步");
person.hobbies().remove("读书");

// Address本身是Record,所以是不可变的
// person.address().street = "新街道"; // 编译错误!Address字段也是final的

System.out.println("修改后的person: " + person);
System.out.println("hobbies被修改了,但address保持不变");
System.out.println();
}

private static void demonstrateSolutions() {
System.out.println("4. 实现真正不可变的解决方案:");

// 方案1:防御性复制
System.out.println("方案1 - 防御性复制:");
List<Integer> originalGrades = new ArrayList<>(Arrays.asList(85, 92, 78));
SafeStudent safeStudent = new SafeStudent("安全学生", originalGrades);
System.out.println("创建安全学生: " + safeStudent);

// 尝试修改原始列表
originalGrades.add(100);
System.out.println("修改原始列表后,安全学生不受影响: " + safeStudent);

// 尝试修改返回的列表
try {
safeStudent.grades().add(95);
} catch (UnsupportedOperationException e) {
System.out.println("无法修改返回的grades列表(只读)");
}

System.out.println();

// 方案2:使用不可变集合
System.out.println("方案2 - 使用不可变集合:");
ImmutableStudent immutableStudent = new ImmutableStudent(
"不可变学生",
List.of(85, 92, 78) // 创建不可变列表
);
System.out.println("创建不可变学生: " + immutableStudent);

try {
immutableStudent.grades().add(95);
} catch (UnsupportedOperationException e) {
System.out.println("无法修改不可变列表");
}
}

public static void main(String[] args) {
demonstrateShallowImmutability();
}
}

// 解决方案1:防御性复制
record SafeStudent(String name, List<Integer> grades) {
// 紧凑构造器中进行防御性复制
public SafeStudent {
grades = new ArrayList<>(grades); // 创建副本
}

// 访问器也返回只读视图
public List<Integer> grades() {
return Collections.unmodifiableList(grades);
}
}

// 解决方案2:使用不可变集合
record ImmutableStudent(String name, List<Integer> grades) {
public ImmutableStudent {
// 确保传入的是不可变集合
grades = List.copyOf(grades);
}
}

// 危险的可变Record示例
record MutableDataRecord(StringBuilder text, Date date, List<String> items) {
public void demonstrateMutability() {
System.out.println("=== 可变字段的危险性 ===");

// 所有这些修改都会影响Record的"不可变性"
text.append(" - 被修改了!");
date.setTime(System.currentTimeMillis());
items.add("新添加的项目");

System.out.println("Record的内容被悄悄修改了!");
}
}

Record 类的代码示例

传统方式 vs Record方式

传统的数据类写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class PersonTraditional {
private final String name;
private final int age;
private final String email;

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

public String getName() { return name; }
public int getAge() { return age; }
public String getEmail() { return email; }

@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
PersonTraditional that = (PersonTraditional) obj;
return age == that.age &&
Objects.equals(name, that.name) &&
Objects.equals(email, that.email);
}

@Override
public int hashCode() {
return Objects.hash(name, age, email);
}

@Override
public String toString() {
return "PersonTraditional{name='" + name + "', age=" + age +
", email='" + email + "'}";
}
}

使用Record的简洁写法:

1
public record Person(String name, int age, String email) {}

这两种方式功能完全相同,但Record版本只需要一行代码!

Record 类的基本语法

声明一个 Record 类

1
2
3
public record RecordName(Type1 field1, Type2 field2, ...) {
// 可选的方法体
}

访问器方法

Record的访问器方法不是传统的getXxx()形式,而是直接使用字段名:

1
2
3
4
5
6
7
public record Student(String name, int grade, boolean isHonorStudent) {}

// 使用
Student student = new Student("张三", 95, true);
System.out.println(student.name()); // 张三
System.out.println(student.grade()); // 95
System.out.println(student.isHonorStudent()); // true

Record的高级特性

自定义构造器

紧凑构造器(Compact Constructor):

它没有参数列表,主要用于对 Record 类的字段进行验证和规范化。其实,虽然构造函数可以声明为没有正式形参列表的形式,但是此时默认使用完整的状态参数列表,也就实现了允许构造函数只执行参数的验证和规范化,而省略显式的字段初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public record Temperature(double celsius) {
// 紧凑构造器 - 用于验证和规范化
public Temperature {
if (celsius < -273.15) {
throw new IllegalArgumentException("温度不能低于绝对零度");
}
// 注意:这里不需要赋值,会自动执行
}

// 便利方法
public double fahrenheit() {
return celsius * 9.0 / 5.0 + 32;
}

public double kelvin() {
return celsius + 273.15;
}
}

标准构造器:

标准构造器与传统类的构造器类似,需要显式地定义参数列表,并在构造器内部对字段进行赋值。同时,还可以定义额外的构造器来提供不同的初始化方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public record Point(int x, int y) {
// 标准构造器
public Point(int x, int y) {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("坐标不能为负数");
}
this.x = x;
this.y = y;
}

// 额外的构造器
public Point() {
this(0, 0);
}
}

静态方法和实例方法

静态工厂方法是一种创建对象的常用方式,它可以提供更具描述性的方法名,同时隐藏对象的创建细节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public record Money(int yuan, int jiao, int fen) {
// 静态工厂方法
public static Money of(double amount) {
int totalFen = (int) Math.round(amount * 100);
return new Money(totalFen / 100, (totalFen % 100) / 10, totalFen % 10);
}

// 实例方法
public double toDouble() {
return yuan + jiao * 0.1 + fen * 0.01;
}

public Money add(Money other) {
int totalFen = this.toTotalFen() + other.toTotalFen();
return new Money(totalFen / 100, (totalFen % 100) / 10, totalFen % 10);
}

private int toTotalFen() {
return yuan * 100 + jiao * 10 + fen;
}
}

// 使用示例
Money price1 = Money.of(12.35);
Money price2 = new Money(5, 2, 8);
Money total = price1.add(price2);

实现接口

Record 类可以实现接口,这使得 Record 类可以像普通类一样参与多态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface Drawable {
void draw();
}
// Circle 类实现了 Drawable 接口,并实现了 draw() 方法。
// 通过实现接口,Circle 类可以作为 Drawable 类型的对象使用,从而实现多态。
public record Circle(double radius, String color) implements Drawable {
@Override
public void draw() {
System.out.printf("绘制半径为%.2f的%s圆形%n", radius, color);
}

public double area() {
return Math.PI * radius * radius;
}
}

继承和嵌套 Record 类

虽然 Record 类本身是隐式 final 的,不能被继承,但可以在 Record 类中嵌套使用 Record 类,以创建更复杂的数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 嵌套 Record 类示例
public record Address(String street, String city, String zipCode) {}

public record Person(String name, int age, Address address) {
// 可以在外部 Record 类中定义方法来操作嵌套的 Record 类
public String getFullAddress() {
return address.street() + ", " + address.city() + ", " + address.zipCode();
}
}

public class NestedRecordExample {
public static void main(String[] args) {
Address address = new Address("中山路123号", "北京", "100000");
Person person = new Person("张三", 25, address);

System.out.println("姓名: " + person.name());
System.out.println("年龄: " + person.age());
System.out.println("地址: " + person.getFullAddress());
}
}

Record与泛型

Record完全支持泛型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public record Pair<T, U>(T first, U second) {
public static <T, U> Pair<T, U> of(T first, U second) {
return new Pair<>(first, second);
}
}

public record Result<T>(boolean success, T data, String message) {
public static <T> Result<T> success(T data) {
return new Result<>(true, data, null);
}

public static <T> Result<T> failure(String message) {
return new Result<>(false, null, message);
}
}

// 使用示例
Pair<String, Integer> nameAge = Pair.of("李四", 28);
Result<List<String>> result = Result.success(Arrays.asList("a", "b", "c"));

record关键字实现密封接口的代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public sealed interface Animal permits Cat, Dog{
String eat();
}

public record Cat(String name) implements Animal {
@Override
public String eat() {
return name+"吃鱼";
}
}

public record Dog(String name) implements Animal {
@Override
public String eat() {
return name+"吃骨头";
}
}

record关键字支持使用注解的代码示例

1
2
3
public record Dept(@Deprecated int id,@Deprecated String deptName) {

}

record 关键字代码示例的总结说明

  • record关键字类似enum,也是一种特殊的class。
  • record会自动生成一个隐式的带参构造方法,默认没有无参构造方法,但 record 允许声明没有参数列表的构造方法,默认使用完整的状态参数列表,这允许构造函数只执行参数的验证和规范化,而省略显式的字段初始化。
  • record会自动为每个状态生成一个同名的get方法,注意方法名与变量名同名,不是getXxx。都是final字段自然没有set方法。
  • record会自动根据状态字段重写hashcode、equals以及toString方法。
  • record没有extend子句,所有record都直接继承java.lang.Record,但可以实现接口。
  • record是隐式final的,不能有抽象方法,不可再被继承,以防止被子类扩展方法改变状态。
  • record不能定义native方法,以防止record记录的状态被外部行为影响。

Record的限制

不能做的事情

  1. 不能继承其他类(但可以实现接口)
  2. 不能被继承(隐式final)
  3. 不能声明实例字段(除了组件字段)
  4. 组件字段都是final的

可以做的事情

  1. 实现接口
  2. 定义静态字段和方法
  3. 定义实例方法
  4. 使用泛型
  5. 定义嵌套类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public record DataContainer<T>(T data) {
// 静态字段
private static final String DEFAULT_PREFIX = "DATA_";

// 静态方法
public static <T> DataContainer<T> empty() {
return new DataContainer<>(null);
}

// 实例方法
public boolean isEmpty() {
return data == null;
}

public String getFormattedData() {
return DEFAULT_PREFIX + (data != null ? data.toString() : "NULL");
}

// 嵌套记录
public record Metadata(String author, LocalDateTime created) {}
}

性能考虑

Record类具有以下性能特征:

  1. 内存效率:没有额外的开销,字段直接存储
  2. 创建效率:构造器性能与普通类相同
  3. equals/hashCode优化:JVM可能对这些方法进行特殊优化
  4. 不可变性:线程安全,可以安全地缓存hashCode

Record为Java带来了现代化的数据建模方式,大大减少了样板代码,提高了代码的可读性和维护性。它特别适合用于创建简单的数据容器、DTO对象和值对象,是Java语言演进中的一个重要里程碑。