Java21新特性尝鲜使用
Java21在九月份已经正式的变成了 GA版本,同时也是接替 Java17的下一个 LTS版本。本文就参照官方文档尝鲜使用一下发布的新特性。发行说明
字符串模版
以往的 Java代码中,当遇到字符串拼接的场景时,几乎都是使用 +或者 append()方法。代码简洁程度和可读性都不如类似于 Js的 ${}字符串模版。
在 Jdk21中也带来了 Java的字符串模版类 StringTemplate,字符串中插入变量使用 \{}。
import static java.lang.StringTemplate.STR;
public class Example {
public static void main(String[] args) {
String name = "xin";
// STR是静态类型
String message = STR."hello \{name}";
// messag == hello xin
System.out.println(message);
}
}
表达式中可以使用方法或自操作符
public class Example {
public static void main(String[] args) {
int count = 0;
String name = "xin";
String message = STR."hello \{name}, timestamp:\{System.currentTimeMillis()}, count:\{++count}, boolean:\{count == 2}";
System.out.println(message);
}
}
文本块中也可使用
public class Example {
public static void main(String[] args) {
String name = "xin";
int age = 24;
String json = STR."""
{
"name": "\{name}",
"age": \{age}
}
""";
String html = STR."""
<html>
<body>
<div>
\{name}
</div>
<div>
\{age}
</div>
</body>
</html
""";
System.out.println(json);
System.out.println(html);
}
}
String name = "xin";
// RAW可以返回未解析的字符串模版对象
StringTemplate template = RAW."name: \{name}";
// 调用interpolate()方法可以进行解析 然后返回完成的字符串
String message = template.interpolate();
System.out.println(message);
有序集合
Jdk21新增了 Sequenced Collections接口来简化集合处理,解决了需要访问集合的第一个或最后一个元素以及需要向集合首部和尾部添加元素的问题。
public class Example {
private static final String[] books = {"西游记", "三国演义", "水浒传", "红楼梦"};
public static void main(String[] args) {
// 对于Set List Deque有序集合都能使用
LinkedHashSet<String> strings = new LinkedHashSet<>(Arrays.asList(books));
// 获取集合第一个元素
String first = strings.getFirst();
// 获取集合最后一个元素
String last = strings.getLast();
// 向集合的首部添加元素
strings.addFirst("三体");
first = strings.getFirst();
}
}
新的垃圾回收器(Generational ZGC)
通过 Generational ZGC,可以为新旧对象保留不同的周期,从而提高性能。这将让 ZGC更频繁的收集年轻对象。
使用 Generational ZGC的应用程序应该具有以下特性
- 降低分配停滞的风险
- 降低所需的堆内存消耗
- 降级垃圾回收器CPU性能消耗
使用
Generational ZGCjava -XX:+UseZGC -XX:+ZGenerational
虚拟线程(Virtual Threads)
Jdk21引入了虚拟线程,虚拟线程是轻量级线程,不与CPU线程绑定。类似于Go中的 goroutine。
虚拟线程成本低廉且数量充足,因此不应该被池化。应该为每个应用程序任务创建一个新的虚拟线程。大多数虚拟线程都是短暂的,具有浅层调用堆栈,只执行单个HTTP请求或JDBC查询。
演示一个典型的获取数据然后拼装的操作如何使用异步优化
public class StudentService {
private static final Map<Integer, Student> studentCache = Map.ofEntries(
Map.entry(1, new Student(1, "张三", 17, 0, null)),
Map.entry(2, new Student(2, "李四", 16, 0, null)),
Map.entry(3, new Student(3, "李雪", 16, 1, null)),
Map.entry(4, new Student(4, "王五", 18, 0, null))
);
public List<Student> listAll() {
wait(2);
return studentCache.values().stream().toList();
}
public Student selectById(Integer id) {
wait(1);
return studentCache.get(id);
}
private static void wait(int s) {
try {
Thread.sleep(Duration.ofSeconds(s));
}catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
public class BookService {
private static final Map<Integer, Book> bookCache = Map.ofEntries(
Map.entry(1, new Book(1, 1, "语文", 11.2)),
Map.entry(2, new Book(2, 2, "数学", 11.3)),
Map.entry(3, new Book(3, 3, "英语", 11.4)),
Map.entry(4, new Book(4, 4, "物理", 11.5))
);
public List<Book> listAll() {
wait(2);
return bookCache.values().stream().toList();
}
public Book selectById(Integer id) {
wait(1);
return bookCache.get(id);
}
private static void wait(int s) {
try {
Thread.sleep(Duration.ofSeconds(s));
}catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
不使用异步优化
public class Example {
private static final ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
private static final StudentService studentService = new StudentService();
private static final BookService bookService = new BookService();
public static void main(String[] args) throws ExecutionException, InterruptedException {
long startTime = System.currentTimeMillis();
Student student = studentService.selectById(1);
Book book = bookService.selectById(1);
StudentBook studentBook = new StudentBook(student.getId(), student.getName(), student.getAge(), student.getGender(), student.getRemark(), book.getId(), book.getName(), book.getPrice());
System.out.println(studentBook);
long endTime = System.currentTimeMillis();
// 耗时 2026 mills
System.out.println(STR."handleTime:\{endTime - startTime}");
}
}
record StudentBook(Integer id, String name, Integer age, Integer gender, String remark, Integer bookId, String bookName, Double bookPrice) {}
使用虚拟线程进行异步优化
public class Example {
private static final ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
private static final StudentService studentService = new StudentService();
private static final BookService bookService = new BookService();
public static void main(String[] args) throws ExecutionException, InterruptedException {
long startTime = System.currentTimeMillis();
// 开启一条虚拟线程异步执行方法
Future<Student> studentFuture = executorService.submit(() -> studentService.selectById(1));
Future<Book> bookFuture = executorService.submit(() -> bookService.selectById(1));
// 使用 get 方法等待数据,应该在数据需要使用时再调用。如果数据没有准备好 会阻塞主线程
Student student = studentFuture.get();
Book book = bookFuture.get();
StudentBook studentBook = new StudentBook(student.getId(), student.getName(), student.getAge(), student.getGender(), student.getRemark(), book.getId(), book.getName(), book.getPrice());
System.out.println(studentBook);
long endTime = System.currentTimeMillis();
System.out.println(STR."handleTime:\{endTime - startTime}");
}
}
record StudentBook(Integer id, String name, Integer age, Integer gender, String remark, Integer bookId, String bookName, Double bookPrice) {}
虚拟线程的异步调用和 CompletableFuture.runAsync()几乎差不多,但是后者有默认的线程池,使用的是物理线程。
自我感觉虚拟线程主要的应用层面还是 Web后端,当中央控制器接收到请求后,开启虚拟线程去执行当前的请求方法,在虚拟线程数量足够多的情况下,理想情况请求几乎不会出现阻塞,能够大幅提高系统并发处理能力。
未命名模式和变量
可以使用 _来声明一个未命名的变量,表示这个变量不会被使用(类似于Go)
String[] strings = new String[] {"1", "2", "3"};
// 忽略变量
for (String _ : strings) {
System.out.println("a");
}
未命名类和实例主方法
实例主方法不需要 static、public也不需要有 String[]参数即可运行
public class Main {
void main() {
System.out.println("Hello world");
}
}
在此基础上,还可以引入未命名类使 class被隐式声明,只需要一个main方法声明即可运行。
void main() {
System.out.println("Hello World");
}
结构化并发
结构化并发是一种增强的并发编程方法,与虚拟线程深度绑定,保留了任务与子任务之间的依赖关系。.....






0 条评论