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
| if (obj instanceof String) { String s = (String) obj; System.out.println(s.length()); }
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) { }
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
| public record ShoppingCart(String userId, List<String> items) {}
ShoppingCart cart = new ShoppingCart("user123", new ArrayList<>());
cart.items().add("苹果"); cart.items().add("香蕉");
|
为什么会这样?
Java的final
关键字只保证:
- 基本类型:值不能改变(真正不可变)
- 引用类型:引用不能改变(但引用的对象内容可能可变)
1 2 3
| final List<String> 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;
public record PersonBasic(String name, int age) {}
public record StudentWithGrades(String name, List<Integer> grades) {}
public record Company(String name, List<String> employees, Map<String, Integer> salaries) {}
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"); demonstrateBasicImmutability(); demonstrateCollectionMutability(); demonstrateNestedObjectMutability(); demonstrateSolutions(); } private static void demonstrateBasicImmutability() { System.out.println("1. 基本数据类型的不可变性:"); PersonBasic person = new PersonBasic("张三", 25); System.out.println("创建person: " + person); 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().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.hobbies().add("跑步"); person.hobbies().remove("读书"); System.out.println("修改后的person: " + person); System.out.println("hobbies被修改了,但address保持不变"); System.out.println(); } private static void demonstrateSolutions() { System.out.println("4. 实现真正不可变的解决方案:"); 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(); 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(); } }
record SafeStudent(String name, List<Integer> grades) { public SafeStudent { grades = new ArrayList<>(grades); } public List<Integer> grades() { return Collections.unmodifiableList(grades); } }
record ImmutableStudent(String name, List<Integer> grades) { public ImmutableStudent { grades = List.copyOf(grades); } }
record MutableDataRecord(StringBuilder text, Date date, List<String> items) { public void demonstrateMutability() { System.out.println("=== 可变字段的危险性 ==="); 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()); System.out.println(student.isHonorStudent());
|
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(); }
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
| public record Address(String street, String city, String zipCode) {}
public record Person(String name, int age, Address address) { 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的限制
不能做的事情
- 不能继承其他类(但可以实现接口)
- 不能被继承(隐式final)
- 不能声明实例字段(除了组件字段)
- 组件字段都是final的
可以做的事情
- 实现接口
- 定义静态字段和方法
- 定义实例方法
- 使用泛型
- 定义嵌套类型
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类具有以下性能特征:
- 内存效率:没有额外的开销,字段直接存储
- 创建效率:构造器性能与普通类相同
- equals/hashCode优化:JVM可能对这些方法进行特殊优化
- 不可变性:线程安全,可以安全地缓存hashCode
Record为Java带来了现代化的数据建模方式,大大减少了样板代码,提高了代码的可读性和维护性。它特别适合用于创建简单的数据容器、DTO对象和值对象,是Java语言演进中的一个重要里程碑。