前言

最近入职了新公司,发现新东家采用的技术栈相当新潮,这让长期“坚守”Java 8 的我颇感意外。正是这份新鲜感,这才催生了这篇博客。
本文无意深入原理,仅对新特性做简要梳理,方便日后查阅与回顾。

新特性

Var局部类型推断

var是Java 10版本引入的新特性,它的核心能力是让编译器根据初始表达式来推断左边的数据类型,而非传统的由程序员手动指定。var有助于减少样板代码,在使用得当的情况下也有助于提高代码可读性,同时它也有它的局限性,比如只能用于局部变量、必须初始化变量、不能用于Lambda表达式等等,下面觉几个简单的例子。

1
2
3
4
5
6
7
8
9
10
static void main() {
var a = 520;
String b = "hello";
System.out.println(b + " " + a);
var c = new HashMap<>();
c.put("name", "张三");
for (var entry : c.entrySet()){
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}

文本块

文本块在JDK15正式引入,主要是解决Java传统的字符串在编写涉及多行内容时不便问题,这些操作往往需要包含换行、转义等行为,导致代码可读性特别差。

文本块以 “”” 开始,以 “”” 结束,下面我们比较下传统的写法,和文本块的写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static  void main() {
String traditionalStr = "{\n" +
" \"name\": \"张三\",\n" +
" \"age\": 30,\n" +
" \"hobbies\": [\"阅读\", \"游泳\"]\n" +
"}";
String textBlockStr = """
{
"name": "张三",
"age": 30,
"hobbies": ["阅读", "游泳"]
}
""";
System.out.println(traditionalStr);
System.out.println(textBlockStr);
}

Java Record(纪录类)

Java中的record类是Java16正式引入特性,被设计为一种承载不可变数据的载体的类。另外,它自动实现了构造器、访问方法、哈希方法、toString()方法等等,使代码开起来会更简洁。

首先看下如何创建Record类

1
2
3
4
5
6
7
8
9
public record UserInfo(Long id, String name, Integer  age) {

public UserInfo {
// 可以自己添加校验逻辑
if (age < 0){
throw new IllegalArgumentException("age can not be less than zero");
}
}
}

构造record对象和普通的通过new关键字创建对象很类似的,只是由于对象中的字段都是不可变的,所以record对象是不能手动调用set赋值的。

1
2
3
4
5
6
7
8
9
10
11
static void main() {
UserInfo user1 = new UserInfo(1L, "张三", 18);
System.out.println(user1);
UserInfo user2 = new UserInfo(2L, "张三", -1);
System.out.println(user2);
}

输出结果为:
UserInfo[id=1, name=张三, age=18]
Exception in thread "main" java.lang.IllegalArgumentException: age can not be less than zero
at new_feature.UserInfo.<init>(UserInfo.java:8)

模式匹配

JDK16引入的一个增强instanceof特性的能力。

1
2
3
4
5
6
7
8
9
10
11
12
void patternMatch(){
Object object = "张三";
// 之前写法
if(object instanceof String){
String name = (String) object;
System.out.println(name);
}
// 现写法, 更简洁
if(object instanceof String name){
System.out.println( name);
}
}

switch模式匹配

在一次次的更新中,switch语法也在不断的优化,在jdk8中我们还需要为每个分支设置break,否则就可能执行多个分支的逻辑,自jdk14后已经不存在这个问题了,并且和模式匹配配合起来能发挥不可思议的作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void patternMatchSwitch(Object object){
System.out.println(switch (object) {
case Integer i -> "数值类型";
case String s -> "字符串";
case UserInfo userInfo when userInfo.id().equals(1L) -> "用户信息: " + userInfo;
default -> "object = " + object;
});
}
static void main() {
UserInfo user1 = new UserInfo(1L, "张三", 18);
UserInfo user2 = new UserInfo(2L, "张三", 1);
patternMatchSwitch(user1);
patternMatchSwitch(user2);
patternMatchSwitch(1);
patternMatchSwitch("张三");
}
结果:
用户信息: UserInfo[id=1, name=张三, age=18]
object = UserInfo[id=2, name=张三, age=1]
数值类型
字符串

Sealed Classes密封类

密封类的目的是提供一个比使用final修饰更灵活,比使用开放继承更安全的继承控制。

在之前的使用方式中,类的继承是“全有或全无”的,全有意味着类可以被任何类继承;全无则是使用final修饰,任何类不允许继承当前类。而密封类则提供了一种特性,允许我们指定特定的类来继承或者实现它。

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
// 定义一个密封的接口 Shape
public sealed interface Shape permits Circle, Rectangle, Square {
double area();
}

// Circle 是 Shape 的直接子类,且是最终的
final class Circle implements Shape {
private final double radius;

public Circle(double radius) {
this.radius = radius;
}

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

// Rectangle 是 Shape 的直接子类,且是最终的
final class Rectangle implements Shape {
private final double width, height;

public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}

@Override
public double area() {
return width * height;
}
}

// Square 是 Shape 的直接子类,但它本身也是密封的
sealed class Square implements Shape permits UnitSquare, CustomSquare {
private final double side;

public Square(double side) {
this.side = side;
}

@Override
public double area() {
return side * side;
}
}

// UnitSquare 是 Square 的子类,且是最终的
final class UnitSquare extends Square {
public UnitSquare() {
super(1.0);
}
}

// CustomSquare 是 Square 的子类,且是开放的
non-sealed class CustomSquare extends Square {
public CustomSquare(double side) {
super(side);
}
// 可以被其他类继承
}

// 尝试创建一个不在 permits 列表中的子类会编译错误
// class Triangle implements Shape { // 编译错误!Shape 是密封的,只允许 Circle, Rectangle, Square
// ...
// }

虚拟线程

虚拟线程是Java 21推出的轻量级线程,由JVM管理。它让数百万并发任务像普通对象一样创建,无需复杂异步编程。特别适合高并发I/O场景,用简单阻塞代码即可实现极高吞吐量,是Java并发的革命性进化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static void virtualThreadTest()  {
// 方式一:通过 Thread.ofVirtual().start() 创建并启动一个虚拟线程
Thread.ofVirtual().start(() -> {
System.out.println("Virtual Thread-1");
});

// 方式二:通过 Thread.startVirtualThread() 直接创建并启动虚拟线程
Thread.startVirtualThread(() -> {
System.out.println("Virtual Thread-2");
});

// 方式三:通过 Thread.ofVirtual().factory() 获取虚拟线程工厂,
// 然后使用 Executors.newThreadPerTaskExecutor() 创建执行器来管理虚拟线程
ThreadFactory threadFactory = Thread.ofVirtual().factory();
try (var executor = Executors.newThreadPerTaskExecutor(threadFactory)){
for (int i = 0; i < 10; i++) {
int finalI = i;
executor.submit(() -> {
System.out.println("VirtualThreadFactory-" + finalI);
});
}
}
}