从JDK 17 到 JDK 21:Java 新特性
JDK17
密封类
概念:密封类允许开发者控制哪些类可以继承或实现特定的类或接口。通过这种方式,密封类为类的继承提供了更高的安全性和可维护性。
定义:使用sealed代表该类为密封类,并用permits限制哪些类可以继承。
public sealed class Shape permits Circle, Square {
// 类体
}
public final class Circle extends Shape {
// 类体
}
public final class Square extends Shape {
// 类体
}
继承类必须由final,sealed,non-sealed修饰。
- final代表该继承类不能被任何类继承
- sealed代表该类依然是继承类
- non-sealed代表该类不受任何限制
优势:
- 增强可维护性: 开发者可以清晰地定义哪些类可以扩展特定类,从而更好地控制代码的变化。
- 提高安全性: 限制类的继承可以防止不受控制的扩展,降低代码出错的概率。
- 更好的设计: 密封类可以使类的层次结构更加明确,符合设计模式的要求。
简化instanceof
在JDK16及以前需要先进行类型判断然后强制转换获取对象
public class Main {
public static void main(String[] args) {
Object obj = "Hello, World!"; // String 类型的对象
if (obj instanceof String) {
String str = (String) obj; // 强制转换
System.out.println(str.toUpperCase()); // 使用转换后的字符串
}
}
}
在JDK17开始可以合二为一
public class Main {
public static void main(String[] args) {
Object obj = "Hello, World!"; // String 类型的对象
if (obj instanceof String str) { // 类型检查和转换
System.out.println(str.toUpperCase()); // 直接使用 str
}
}
}
优点:
- 简洁性: 通过将类型检查和转换合并为一个操作,代码更加简洁,减少了冗余。
- 可读性: 减少了强制转换的显式写法,使得代码更易于理解。
- 减少出错: 避免了在进行强制转换时可能出现的
ClassCastException
异常,因为编译器会确保类型检查的正确性。
增强Switch
基本示例
增强的 switch
表达式允许使用“箭头”语法 (->
),这使得每个 case
分支可以直接返回一个值或执行一段代码。以下是基本的语法示例:
int day = 3; // 假设 1 = 星期一,2 = 星期二,...
String dayType = switch (day) {
case 1, 7 -> "Weekend"; // 支持多个 case 用逗号分隔
case 2, 3, 4, 5, 6 -> "Weekday"; // 其他工作日
default -> throw new IllegalArgumentException("Invalid day: " + day); // 默认情况
};
System.out.println(dayType); // 输出: Weekday
代码块支持
除了简单的返回值,switch
表达式还支持代码块,你可以在 case
中包含多行代码。这时需要使用 {}
来包裹代码块,并且要使用 yield
语句返回值。
public class Main {
public static void main(String[] args) {
int day = 5; // 假设 1 = 星期一,2 = 星期二,...
String dayType = switch (day) {
case 1, 7 -> "Weekend";
case 2, 3, 4, 5, 6 -> "Weekday";
default -> throw new IllegalArgumentException("Invalid day: " + day);
};
System.out.println("Day " + day + " is a " + dayType);
}
}
文本块增强
基本概念
文本块允许以更直观的方式定义多行字符串,使用三重引号("""
)来包裹文本内容。这样,文本块中的换行、缩进和其他空白字符将被直接保留,增强了可读性。
基本用法
基本的文本块用法如下所示:
java复制代码String textBlock = """
This is a text block.
It can span multiple lines.
""";
文本块的增强
在 Java 17 中,对文本块进行了增强,主要体现在以下几个方面:
换行和缩进处理
- 自动去除公共前缀: 文本块的自动缩进特性可以去除公共前缀。这意味着,如果文本块的所有行都有相同的前缀空白(缩进),在生成的字符串中,这些空白将会被自动去除。
示例:
java复制代码String textBlock = """
Line 1
Line 2
Line 3
""";
System.out.println(textBlock);
输出将是:
Line 1
Line 2
Line 3
在这个示例中,公共前缀的空白会被自动去除。
使用表达式插入文本
文本块可以与表达式结合,允许在文本块中插入动态内容。这使得文本块更加灵活和动态。
示例:
java复制代码String name = "John";
String greeting = """
Hello, %s!
Welcome to the Java world.
""".formatted(name);
System.out.println(greeting);
输出将是:
Hello, John!
Welcome to the Java world.
在这个示例中,使用了 String.formatted()
方法来插入变量。
支持 Unicode 字符
文本块支持 Unicode 字符,使得在字符串中包含特殊字符变得更加方便。例如,你可以直接在文本块中使用中文字符、表情符号等。
示例:
String unicodeTextBlock = """
Hello, 世界! 🌍
This is a text block with Unicode characters.
""";
System.out.println(unicodeTextBlock);
输出将是:
Hello, 世界! 🌍
This is a text block with Unicode characters.
JDK21
虚拟线程
为什么引入?
线程的缺点有两个:
- 创建销毁成本高
- 频繁切换成本高
第一个缺点已经通过线程池解决了,第二个在JDK21通过虚拟线程也得到了解决。
概念与优势
虚拟线程被设计为轻量级的执行单元,可以在 JVM 中并发执行。虚拟线程使得开发者能够创建和管理数以千计的并发任务,而无需像使用传统线程那样消耗大量资源。
优势:
- 轻量级: 虚拟线程的内存开销和启动时间都大大低于操作系统线程。这使得创建和管理虚拟线程变得更加高效。
- 易于使用: 开发者可以使用简单的代码结构来编写并发任务,而无需管理线程的生命周期和状态。
- 高并发: 虚拟线程允许在单个 JVM 实例中并发运行数以千计的虚拟线程,非常适合 I/O 密集型和网络应用程序。
使用
虚拟线程在代码上兼容极好,跟传统线程区别不大。
直接创建虚拟线程
public class VirtualThreadExample {
public static void main(String[] args) {
Thread virtualThread = Thread.ofVirtual().start(() -> {
System.out.println("This is a virtual thread!");
});
virtualThread.join();
}
}
通过线程池获取
// 创建一个虚拟线程池
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
// 提交多个任务到虚拟线程池
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.submit(() -> {
});
}
// 关闭线程池
executor.shutdown();
序列集合
在之前的版本中,List、Set、LinkedHashSet这些不同的集合获取首尾元素的方式不同,api不同,很折磨人。
序列集合是一类新接口,与常规集合(如 List
和 Set
)不同,序列集合强调保持元素的插入顺序,并提供了一种更灵活的元素处理方式。
interface sequencedcollection<E>extends colection<E>{
Sequencedco1lection<E> reversed(),
void addLast(E);
E getFirst();
E getLast();
E removeFirst;
E removeLast();
}
有个序列集合后就不用因为不统一的api发愁了。
Record
介绍
定义: Record
是一种特殊的类,用于表示不可变的数据模型。它自动为每个字段生成构造函数、访问器(getter)和其他一些方法(如 toString()
、equals()
和 hashCode()
)。
语法: 创建 Record
类的语法非常简洁,类似于定义普通类。记录类的定义如下:
public record Person(String name, int age) { }
特性
不可变性:
Record
实例是不可变的,一旦创建,字段的值就不能被修改。这确保了数据的一致性和安全性,尤其是在多线程环境中。
自动生成方法:
- 构造函数: 自动生成一个构造函数,接受所有字段作为参数。
- 访问器: 为每个字段生成一个访问器方法,名称与字段相同,但没有前缀。例如,上述
Person
记录将具有name()
和age()
方法。 toString()
: 自动生成一个toString()
方法,返回所有字段的名称和值。equals()
和hashCode()
: 自动实现equals()
和hashCode()
方法,以支持基于内容的比较和集合操作。
JDK21增强特性
record
中的泛型
Record
现在可以定义为泛型类。这使得你能够创建更具通用性和复用性的记录类:
public record Box<T>(T content) { }
sealed
Record
可以将 Record
声明为 sealed
,限制哪些类可以扩展该 Record
:
public sealed record Shape permits Circle, Square { }
public record Circle(double radius) extends Shape { }
public record Square(double side) extends Shape { }
记录的嵌套记录
你可以在 Record
中嵌套另一个 Record
,这使得组织复杂数据模型更为简洁:
public record Address(String street, String city) { }
public record Person(String name, int age, Address address) { }
使用场景
数据传输对象(DTO)
场景描述: DTO 是一种简单的对象,用于封装数据并在不同层或模块之间传递。使用 Record
可以减少样板代码。
示例代码:
// 数据传输对象
public record UserDTO(String username, String email, int age) { }
public class UserService {
public UserDTO getUser(int id) {
// 假设从数据库获取用户数据
return new UserDTO("xxx", "xxx@qq.com", 25);
}
}
public class Main {
public static void main(String[] args) {
UserService userService = new UserService();
UserDTO user = userService.getUser(1);
System.out.println(user.username());
System.out.println(user.email());
System.out.println(user.age());
}
}
返回类型
场景描述: 方法需要返回多个值时,使用 Record
可以将这些值组合在一起,避免使用数组或其他复杂的数据结构。
配置和设置
场景描述: 在需要表示一组配置参数或设置选项时,Record
可以提供清晰和易于维护的表示方式。
示例代码:
// 配置类
public record DatabaseConfig(String url, String username, String password) { }
总结
通过记录我们可以把之前需要创建类或者构建复杂数据结构的场景简化,记录会自动创建一些方法供我们使用,所以只需要简单声明记录类即可。