一起来学习下 Java 20 的新功能
Amber 项目
switch 模式匹配 Record 模式
Amber 项目的目标是探索和孵化更小、以生产力为导向的 Java 语言功能。
static String apply(Effect effect) {
String formatted = "";
if (effect instanceof Delay de) {
formatted = String.format("Delay active of %d ms.", de.timeInMs());
} else if (effect instanceof Reverb re) {
formatted = String.format("Reverb active of type %s and roomSize %d.", re.name(), re.roomSize());
} else if (effect instanceof Overdrive ov) {
formatted = String.format("Overdrive active with gain %d.", ov.gain());
} else if (effect instanceof Tremolo tr) {
formatted = String.format("Tremolo active with depth %d and rate %d.", tr.depth(), tr.rate());
} else if (effect instanceof Tuner tu) {
formatted = String.format("Tuner active with pitch %d. Muting all signal!", tu.pitchInHz());
} else {
formatted = String.format("Unknown effect active: %s.", effect);
}
return formatted;
}
static String apply(Effect effect) {
return switch(effect) {
case Delay de -> String.format("Delay active of %d ms.", de.timeInMs());
case Reverb re -> String.format("Reverb active of type %s and roomSize %d.", re.name(), re.roomSize());
case Overdrive ov -> String.format("Overdrive active with gain %d.", ov.gain());
case Tremolo tr -> String.format("Tremolo active with depth %d and rate %d.", tr.depth(), tr.rate());
case Tuner tu -> String.format("Tuner active with pitch %d. Muting all signal!", tu.pitchInHz());
case null, default -> String.format("Unknown or empty effect active: %s.", effect);
};
}
static String apply(Effect effect, Guitar guitar) {
return switch(effect) {
case Delay de -> String.format("Delay active of %d ms.", de.timeInMs());
case Reverb re -> String.format("Reverb active of type %s and roomSize %d.", re.name(), re.roomSize());
case Overdrive ov -> String.format("Overdrive active with gain %d.", ov.gain());
case Tremolo tr -> String.format("Tremolo active with depth %d and rate %d.", tr.depth(), tr.rate());
case Tuner tu when !guitar.isInTune() -> String.format("Tuner active with pitch %d. Muting all signal!", tu.pitchInHz());
case Tuner tu -> "Guitar is already in tune.";
case null, default -> String.format("Unknown or empty effect active: %s.", effect);
};
}
这里的 guard 确保复杂的布尔逻辑仍然可以用简洁的方式表示。在 case 分支中嵌套 if 语句来测试这种逻辑不仅更冗长,而且还可能引入我们一开始想避免的小错误。
Java 19 和 Java 20 相比有什么不同
与 Java 19 相比,这个功能做了一些改变:
当一个模式匹配突然失败时,现在会抛出一个 MatchException 在 switch 表达式和语句中支持推断 record 模式的类型参数。这意味着现在可以在要匹配的模式中使用 var
JEP 432:Record 模式(第二预览版)
static String apply(Effect effect) {
return switch(effect) {
case Delay(int timeInMs) -> String.format("Delay active of %d ms.", timeInMs);
case Reverb(String name, int roomSize) -> String.format("Reverb active of type %s and roomSize %d.", name, roomSize);
case Overdrive(int gain) -> String.format("Overdrive active with gain %d.", gain);
case Tremolo(int depth, int rate) -> String.format("Tremolo active with depth %d and rate %d.", depth, rate);
case Tuner(int pitchInHz) -> String.format("Tuner active with pitch %d. Muting all signal!", pitchInHz);
case null, default -> String.format("Unknown or empty effect active: %s.", effect);
};
}
record Tuner(int pitchInHz, Note note) implements Effect {}
record Note(String note) {}
class TunerApplier {
static String apply(Effect effect, Guitar guitar) {
return switch(effect) {
case Tuner(int pitch, Note(String note)) -> String.format("Tuner active with pitch %d on note %s", pitch, note);
};
}
}
class TunerApplier {
static String apply(Effect effect, Guitar guitar) {
return switch(effect) {
case Tuner(var pitch, Note(var note)) -> String.format("Tuner active with pitch %d on note %s", pitch, note);
};
}
}
这里对于嵌套模式 Tuner(var pitch, Note(var note)) 的类型参数进行了推断。目前仅支持嵌套模式的隐式类型推断;类型模式尚不支持隐式类型参数的推断。因此,类型模式 Tuner tu 总是被视为原始类型模式。
增强的 for 语句
记录模式现在也被允许用于增强的 for 语句中,这使得循环遍历记录值的集合并快速提取每个记录的组件变得容易:
record Delay(int timeInMs) implements Effect {}
class DelayPrinter {
static void printDelays(List<Delay> delays) {
for (Delay(var timeInMs) : delays) {
System.out.println("Delay found with timeInMs=" + timeInMs);
}
}
}
Java 19 与 Java 20 的不同之处
添加对泛型记录模式类型参数推断的支持; 添加对记录模式出现在增强for语句标题中的支持。
虚拟线程 有作用域的值 结构化并发
Loom 项目旨在通过引入虚拟线程和结构化并发 API 等方式来简化 Java 中并发应用程序的维护。
JEP 436:虚拟线程(第二个预览版)
典型用例
创建虚拟线程
Response handle() throws IOException {
String theUser = findUser();
int theOrder = fetchOrder();
return new Response(theUser, theOrder);
}
Response handle() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future < String > user = scope.fork(() - > findUser());
Future < Integer > order = scope.fork(() - > fetchOrder());
scope.join(); // Join both forks
scope.throwIfFailed(); // ... and propagate errors
// Here, both forks have succeeded, so compose their results
return new Response(user.resultNow(), order.resultNow());
}
}
注意:Java 19 中的 ExecutorService 接口进行了调整,扩展了 AutoCloseable。因此,现在可以在 try-with-resources 结构中使用它。
与 Java 19 有何不同
此功能处于“第二个预览版”阶段,以便获得更多反馈。除此之外,还有一些API更改已经成为常规功能,不再提供预览。这是因为它们涉及的功能通常很有用,不仅适用于虚拟线程,还包括:
Thread 类中的新方法: join(Duration); sleep(Duration); threadId(). Future 中的新方法(用于检查任务状态和结果) ExecutorService 扩展 AutoCloseable,以便可以在 try-with-resources 块中使用
JEP 429: 作用域值 Scoped Value(孵化器)
ThreadLocal
作用域值
final static ScopedValue < ... > V = ScopedValue.newInstance();
// In some method
ScopedValue.where(V, < value > )
.run(() - > { ...V.get()...call methods...
});
// In a method called directly or indirectly from the lambda expression
...V.get()...
典型应用场景
与 Java 19 有什么不同
JEP 437:结构化并发(第二个孵化器版本)
Future<String> user = executor.submit(() -> findUser());
Future<Integer> order = executor.submit(() -> fetchOrder());
String theUser = user.get(); // Join findUser
int theOrder = order.get(); // Join fetchOrder
return new Response(theUser, theOrder);
}
Response handle() throws ExecutionException, InterruptedException {
Future < String > user = executor.submit(() - > findUser());
Future < Integer > order = executor.submit(() - > fetchOrder());
String theUser = user.get(); // Join findUser int theOrder = order.get(); // Join fetchOrder return new Response(theUser, theOrder);}
Response handle() throws IOException {
String theUser = findUser();
int theOrder = fetchOrder();
return new Response(theUser, theOrder);
}
Response handle() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future < String > user = scope.fork(() - > findUser());
Future < Integer > order = scope.fork(() - > fetchOrder());
scope.join(); // Join both forks scope.throwIfFailed(); // ... and propagate errors // Here, both forks have succeeded, so compose their results return new Response(user.resultNow(), order.resultNow()); }}
在结构化并发中,子任务代表任务工作。任务等待子任务的结果并监视它们的失败。StructuredTaskScope 类允许开发人员将任务结构化为并发子任务家族,并将它们作为一个单元协调。子任务通过单独分叉它们在自己的线程中执行,然后将它们作为一个单元加入并可能作为一个单元取消。父任务处理子任务的成功结果或异常。
与原始示例相比,这里涉及的线程的生命周期理解起来很容易。在所有情况下,它们的生命周期都限于词法作用域,即 try-with-resources 语句的主体。此外,使用 StructuredTaskScope 确保了许多有价值的属性:
短路错误处理:如果子任务失败,则另一个子任务将被取消(如果它尚未完成)。这由 ShutdownOnFailure 实现的取消策略管理;其他策略,如 ShutdownOnSuccess 也是可用的。 取消传播:如果在调用 join() 之前或期间中断运行 handle() 的线程,则当该线程退出作用域时,两个分叉将自动取消。 清晰可见:上面的代码具有明确的结构:设置子任务,等待它们完成或被取消,然后决定是成功(并处理子任务的结果,这些结果已经完成)还是失败(子任务已经完成,因此没有更多的清理工作)。
与 Java 19 有何不同
Panama 项目
外部函数与内存 API Vector API
JEP 434:外部函数 & 内存 API(第二个预览版)
示例代码
// 1. Find foreign function on the C library path
Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();
MethodHandle radixsort = linker.downcallHandle(stdlib.find("radixsort"), ...);
// 2. Allocate on-heap memory to store four strings
String[] javaStrings = { "mouse", "cat", "dog", "car" };
// 3. Use try-with-resources to manage the lifetime of off-heap memory
try (Arena offHeap = Arena.openConfined()) {
// 4. Allocate a region of off-heap memory to store four pointers
MemorySegment pointers = offHeap.allocateArray(ValueLayout.ADDRESS, javaStrings.length);
// 5. Copy the strings from on-heap to off-heap
for (int i = 0; i < javaStrings.length; i++) {
MemorySegment cString = offHeap.allocateUtf8String(javaStrings[i]);
pointers.setAtIndex(ValueLayout.ADDRESS, i, cString);
}
// 6. Sort the off-heap data by calling the foreign function
radixsort.invoke(pointers, javaStrings.length, MemorySegment.NULL, '\0');
// 7. Copy the (reordered) strings from off-heap to on-heap
for (int i = 0; i < javaStrings.length; i++) {
MemorySegment cString = pointers.getAtIndex(ValueLayout.ADDRESS, i);
javaStrings[i] = cString.getUtf8String(0);
}
} // 8. All off-heap memory is deallocated here
assert Arrays.equals(javaStrings, new String[] {"car", "cat", "dog", "mouse"}); // true
让我们仔细看看代码中用到的一些类型,了解它们在外部函数和内存 API 中的功能和目的:
Linker: 提供了从Java代码访问外部函数和从外部函数访问Java代码的功能。它通过downcall方法句柄允许Java代码链接到外部函数。它还通过生成upcall stubs允许外部函数调用Java方法句柄。有关更多信息,请参见此类型的JavaDoc。 SymbolLookup: 检索一个或多个库中符号的地址。有关更多信息,请参见此类型的JavaDoc。 Arena: 控制内存段的生命周期。Arena具有称为竞技场范围的范围。当竞技场关闭时,竞技场范围不再存在。因此,与竞技场范围关联的所有段都无效,它们的支撑内存区域被释放(在适用的情况下),并且在竞技场关闭后不能再访问它们。有关更多信息,请参见此类型的JavaDoc。 MemorySegment: 提供对连续内存区域的访问。有两种类型的内存段:heap segments(在Java内存堆中)和native segments(在Java内存堆之外)。有关更多信息,请参见此类型的JavaDoc。 ValueLayout: 对基本数据类型的值进行建模,例如integral值、floating-point值和address值。除此之外,它还为Java原始类型和地址定义了有用的值布局常量。有关更多信息,请参见此类型的JavaDoc。
与 Java 19 有何不同
合并了 MemorySegment 和 MemoryAddress 抽象(内存地址现在由零长度的内存段建模); 密封的 MemoryLayout 层次结构得到了增强,以便与 switch 模式匹配一起使用; MemorySession 已被分成 Arena 和 SegmentScope,以便跨维护边界共享段。
JEP 438 Vector API(第五个孵化器版本)
示例代码
void scalarComputation(float[] a, float[] b, float[] c) {
for (int i = 0; i < a.length; i++) {
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
}
}
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
void vectorComputation(float[] a, float[] b, float[] c) {
int i = 0;
int upperBound = SPECIES.loopBound(a.length);
for (; i < upperBound; i += SPECIES.length()) {
// FloatVector va, vb, vc;
var va = FloatVector.fromArray(SPECIES, a, i);
var vb = FloatVector.fromArray(SPECIES, b, i);
var vc = va.mul(va)
.add(vb.mul(vb))
.neg();
vc.intoArray(c, i);
}
for (; i < a.length; i++) {
c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
}
}
从 Java 开发者的角度来看,这只是表达标量计算的另一种方式。它可能会显得略微冗长,但另一方面它可以带来惊人的性能提升。
典型用例
Vector API 提供了一种在 Java 中编写复杂向量算法的方法,例如向量化的 hashCode 实现或专门的数组比较。许多领域可以从中受益,包括机器学习、线性代数、加密、文本处理、金融和 JDK 本身的代码。
与 Java 19 有何不同
除了一小部分错误修复和性能增强外,这个特性与 Valhalla 项目的对齐是与 Java 19 最大的不同之处。它的意义是非常明确的,因为向量 API 和 Valhalla 项目都专注于性能提升。
回想一下,Valhalla 项目的目标是通过值对象和用户自定义原语增强 Java 对象模型,将面向对象编程的抽象与简单原语的性能特性相结合。
一旦 Valhalla 项目的功能可用,将会适应 Vector API 以利用值对象,届时它将被提升为预览功能。
转自:Hanno Embregts,
链接:foojay.io/today/its-java-20-release-day-heres-whats-new/
END
往期精彩Spring Boot + MDC 实现全链路调用日志跟踪
一种非侵入式幂等性的Java实现
Logback 自定义日志脱敏组件
Spring Boot + OpenAI 生成图像