Hello World

吞风吻雨葬落日 欺山赶海踏雪径

0%

Java 17 新特性

jdk 17 主要的新特性参考:

openjdk JDK 17

baeldung New Features in Java 17

Java 17 新功能介绍(LTS)

JDK 17 的新特性

Java 17 于2021 年 9 月 14 日发布,包含新特性:

Restore Always-Strict Floating-Point Semantics(JEP 306)

这个特性主要是为了科学计算的应用,它可以保证浮点运算始终是严格的。在 Java SE 1.2 之前,所有的浮点计算都是严格的,但是以当初的情况来看,过于严格的浮点计算在当初流行的 x86 架构和 x87 浮点协议处理器上运行,需要大量的额外的指令开销,所以在 Java SE 1.2 开始,需要手动使用关键字 strictfp(_strict float point_) 才能启用严格的浮点计算。
但是在 2021 年的今天,硬件早已发生巨变,当初的问题已经不存在了,所以从 Java 17 开始,恢复了始终严格的浮点语义这一特性。
严格的浮点语义保证了再所有平台,计算结果都是同样的结果

扩展:strictfp 是 Java 中的一个关键字,大多数人可能没有注意过它,它可以用在接口或者方法上,被 strictfp 修饰的部分中的 float 和 double 表达式会进行严格浮点计算。

1
2
3
4
5
6
7
8
@Test
public strictfp void testStrictfp(){
float aFloat = 0.66666666f;
double aDouble = 0.88888888d;
double sum = aFloat + aDouble;
// 1.5555555665348817
System.out.println("sum: " + sum);
}

java 17 就不需要再使用 strictfp 这个关键字了。

Enhanced Pseudo-Random Number Generators(JEP 356)

JEP 356 为伪随机数生成器(Pseudo-Random Number Generators (PRNG))提供了新的接口与实现,让开发者可以更简单的使用不同的算法,且为流式编程提供了更好的支持。

java 17 这次增加了 RandomGenerator 接口,为所有的 PRNG 算法提供统一的 API,并且可以获取不同类型的 PRNG 对象流。同时也提供了一个新类 RandomGeneratorFactory 用于构造各种 RandomGenerator 实例,在 RandomGeneratorFactory 中使用 ServiceLoader.provider 来加载各种 PRNG 实现。

1
2
3
4
5
6
7
public IntStream getPseudoInts(String algorithm, int streamSize) {
// returns an IntStream with size @streamSize of random numbers generated using the @algorithm
// where the lower bound is 0 and the upper is 100 (exclusive)
return RandomGeneratorFactory.of(algorithm)
.create()
.ints(streamSize, 0,100);
}

例子,创建5个0-10(不包含)的随机数。

1
2
3
4
5
6
7
8
9
@Test
public void testPrng(){
RandomGeneratorFactory<RandomGenerator> l128X256MixRandom = RandomGeneratorFactory.of("L128X256MixRandom");
// 使用时间戳作为随机数种子
RandomGenerator randomGenerator = l128X256MixRandom.create(System.currentTimeMillis());
for (int i = 0; i < 5; i++) {
System.out.print(randomGenerator.nextInt(10)+"\t");
}
}

查看所有的随机数生成
算法:

1
2
3
4
5
6
@Test
public void testAllPrng(){
RandomGeneratorFactory.all().forEach(factory -> {
System.out.println(factory.group() + ":" + factory.name());
});
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
LXM:L32X64MixRandom
LXM:L128X128MixRandom
LXM:L64X128MixRandom
Legacy:SecureRandom
LXM:L128X1024MixRandom
LXM:L64X128StarStarRandom
Xoshiro:Xoshiro256PlusPlus
LXM:L64X256MixRandom
Legacy:Random
Xoroshiro:Xoroshiro128PlusPlus
LXM:L128X256MixRandom
Legacy:SplittableRandom
LXM:L64X1024MixRandom

老的算法 Legacy:Random 也在其中,新的 API 兼容了老的 Random 方式,所以你也可以使用新的 API 调用 Random 类生成随机数。

1
2
3
4
5
6
RandomGeneratorFactory<RandomGenerator> random = RandomGeneratorFactory.of("Random");
// 使用时间戳作为随机数种子
RandomGenerator randomGenerator2 = random.create(System.currentTimeMillis());
for (int i = 0; i < 5; i++) {
System.out.print(randomGenerator2.nextInt(10)+"\t");
}

New macOS Rendering Pipeline (JEP 382)

macOS 为了提高图形的渲染性能,在 2018 年 9 月抛弃了之前 Swing GUI 的 OpenGL 渲染库(macOS 10.14) ,而使用了 Apple Metal 进行代替。
Java 17 这次更新开始支持 Apple Metal,不过对于 API 没有任何改变,这一些都是内部修改。

macOS/AArch64 Port (JEP 391)

起因是 Apple 在 2020 年 6 月的 WWDC 演讲中宣布,将开启一项长期的将 Macintosh(麦金托什) 计算机系列从 x64 过度到 AArch64 的长期计划,因此需要尽快的让 JDK 支持 macOS/AArch64

Linux 上的 AArch64 支持以及在 Java 16 时已经支持。

ps. 英特尔虽然当年打赢了指令集之战,但CISC终究不是未来,诚然为了兼容主流PC依然还是会继续使用CISC,但是新的领域与封闭的场景还是会逐渐迁移掉的。

Deprecate the Applet API for Removal (JEP 398)

Applet 是使用 Java 编写的可以嵌入到 HTML 中的小应用程序,嵌入方式是通过普通的 HTML 标记语法,由于早已过时,几乎没有场景在使用了。
好多浏览器也已经移除了对其的支持,java 9中就已经不建议使用了,java 17正式删除了。

ps. 岁月啊…当年学java的时候还是重点学了Applet

Strongly Encapsulate JDK Internals (JEP 403)

JEP403对JDK内部封装的又进一步增强,直接删除了–illegal-access 参数,如果设置了,平台将忽略该标志并且打印停用的警告。

1
2
3
4
5
6
➜  bin ./java -version
openjdk version "17" 2021-09-14
OpenJDK Runtime Environment (build 17+35-2724)
OpenJDK 64-Bit Server VM (build 17+35-2724, mixed mode, sharing)
➜ bin ./java --illegal-access=warn
OpenJDK 64-Bit Server VM warning: Ignoring option --illegal-access=warn; support was removed in 17.0

除了sun.misc.Unsafe 这种关键API, 其他的内部API 都禁止访问了。

Pattern Matching for Switch (Preview) (JEP 406)

instanceof 一样,为 switch 也增加了类型匹配自动转换功能。
instanceof中可以直接

1
2
3
4
if (obj instanceof String s) {
// Let pattern matching do the work!
...
}

现在switch也支持了这种模式转化

1
2
3
4
5
6
7
8
9
static String formatterPatternSwitch(Object o) {
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}

switch同时也增加了对null值得支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Java 17 之前
static void testFooBar(String s) {
if (s == null) {
System.out.println("oops!");
return;
}
switch (s) {
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
// Java 17
static void testFooBar(String s) {
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}

另一个例子,复杂的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

static record Human (String name, int age, String profession) {}

public String checkObject(Object obj) {
return switch (obj) {
case Human h -> "Name: %s, age: %s and profession: %s".formatted(h.name(), h.age(), h.profession());
case Circle c -> "This is a circle";
case Shape s -> "It is just a shape";
case null -> "It is null";
default -> "It is an object";
};
}

public String checkShape(Shape shape) {
return switch (shape) {
case Triangle t && (t.getNumberOfSides() != 3) -> "This is a weird triangle";
case Circle c && (c.getNumberOfSides() != 0) -> "This is a weird circle";
default -> "Just a normal shape";
};
}

Remove RMI Activation (JEP 407)

RMI Activation 在 Java 15 中的 JEP 385 已经被标记为过时废弃,至今没有收到不良反馈,因此决定在 Java 17 中正式移除。

Sealed Classes (JEP 409)

Sealed Classes 在 Java 15 中的 JEP 360 中提出,在 Java 16 中的 JEP 397 再次预览,现在 Java 17 中成为正式的功能,相比 Java 16 并没有功能变化,这里不再重复介绍。

补充下 Sealed classes 是Project Amber其中的一部分,Project Amber 的目标是探索和培育更小的、面向生产力的Java语言特性,这些特性已被OpenJDKJEP过程中的候选JEP所接受。
大多数Project Amber功能在成为Java平台的正式组成部分之前,至少要经过两轮预览。对于给定的特性,每一轮预览和最终标准化都有单独的JEP。
比如 switch 的模式匹配,文本块等都是这个项目中的一部分。

Remove the Experimental AOT and JIT Compiler (JEP 410)

在 Java 9 和 Java 10 中分别引入的 Ahead-Of-Time (AOT) JEP 295编译与Just-In-Time (JIT) 编译器JEP-317 花费了大量人力来维护,但用处却不大,所以Java 17决定删除这些特性。

主要移除了三个 JDK 模块:

  • jdk.aot - jaotc 工具。
  • Jdk.internal.vm.compiler - Graal 编译器。
  • jdk.internal.vm.compiler.management

Deprecate the Security Manager for Removal (JEP 411)

Security Manager 在 JDK 1.0 时就已经引入,但是它一直都不是保护服务端以及客户端 Java 代码的主要手段,没啥价值,删了。

Foreign Function and Memory API (Incubator) (JEP 412)

新的 API 允许 Java 开发者与 JVM 之外的代码和数据进行交互,通过调用外部函数,可以在不使用 JNI 的情况下调用本地库。

这是一个孵化功能;需要添加 --add-modules jdk.incubator.foreign 来编译和运行 Java 代码。

历史:

  • Java 14 JEP 370 引入了外部内存访问 API(孵化器)。
  • Java 15 JEP 383 引入了外部内存访问 API(第二孵化器)。
  • Java 16 JEP 389 引入了外部链接器 API(孵化器)。
  • Java 16 JEP 393 引入了外部内存访问 API(第三孵化器)。
  • Java 17 JEP 412 引入了外部函数和内存 API(孵化器)。

比如在java中加载c的库

1
2
3
4
5
6
7
8
9

private static final SymbolLookup libLookup;

static {
// loads a particular C library
var path = JEP412.class.getResource("/print_name.so").getPath();
System.load(path);
libLookup = SymbolLookup.loaderLookup();
}

通过对应的API加载到库,然后通过对应的方法签名找到目标方法,最后去执行它。操作如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public String getPrintNameFormat(String name) {

var printMethod = libLookup.lookup("printName");

if (printMethod.isPresent()) {
var methodReference = CLinker.getInstance()
.downcallHandle(
printMethod.get(),
MethodType.methodType(MemoryAddress.class, MemoryAddress.class),
FunctionDescriptor.of(CLinker.C_POINTER, CLinker.C_POINTER)
);

try {
var nativeString = CLinker.toCString(name, newImplicitScope());
var invokeReturn = methodReference.invoke(nativeString.address());
var memoryAddress = (MemoryAddress) invokeReturn;
return CLinker.toJavaString(memoryAddress);
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
}
throw new RuntimeException("printName function not found.");
}

Vector API (Second Incubator) (JEP 414)

java 16中有介绍,矢量API ,这是第二次孵化。

简单的例子说明下矢量相乘:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public void newVectorComputation(float[] a, float[] b, float[] c) {
for (var i = 0; i < a.length; i += SPECIES.length()) {
var m = SPECIES.indexInRange(i, a.length);
var va = FloatVector.fromArray(SPECIES, a, i, m);
var vb = FloatVector.fromArray(SPECIES, b, i, m);
var vc = va.mul(vb);
vc.intoArray(c, i, m);
}
}

public void commonVectorComputation(float[] a, float[] b, float[] c) {
for (var i = 0; i < a.length; i ++) {
c[i] = a[i] * b[i];
}
}

Context-Specific Deserialization Filters (JEP 415)

首先是在 JDK 9 中被引进,可以使我们验证一下从不可信来源传入的序列化数据。反序列化一直是安全的重灾区,我们无法验证反序列化的数据是否是安全的,所以JEP 415 允许在反序列化时,通过一个过滤配置,来告知本次反序列化允许或者禁止操作的类,反序列化时碰到被禁止的类,则会反序列化失败。

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
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputFilter;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class JEP415 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Dog dog = new Dog("哈士奇");
dog.setPoc(new Poc());
// 序列化 - 对象转字节数组
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);) {
objectOutputStream.writeObject(dog);
}
byte[] bytes = byteArrayOutputStream.toByteArray();
// 反序列化 - 字节数组转对象
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
// 允许 com.wdbyte.java17.Dog 类,允许 java.base 中的所有类,拒绝其他任何类
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"com.wdbyte.java17.Dog;java.base/*;!*");
objectInputStream.setObjectInputFilter(filter);
Object object = objectInputStream.readObject();
System.out.println(object.toString());
}
}

class Dog implements Serializable {
private String name;
private Poc poc;

public Dog(String name) {
this.name = name;
}

@Override
public String toString() {
return "Dog{" + "name='" + name + '\'' + '}';
}
// get...set...
}

class Poc implements Serializable{
}

这时反序列化会得到异常:

1
2
3
4
5
Exception in thread "main" java.io.InvalidClassException: filter status: REJECTED
at java.base/java.io.ObjectInputStream.filterCheck(ObjectInputStream.java:1412)
at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2053)
at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1907)
....

其他

Java 17 是个 LTS 版本,22年最新统计,国外使用最广泛的版本是Java 11,大概也是发布时间的原因的问题。

虽然我本人还是很看好Java 17的(毕竟都被Java之父带了波节奏了,Spring 6.0 也说最低支持Java 17),但是它是否能成为超越Java 8的经典存在,我觉得还是有差距的,到不如期待下一个 LTS 版本 Java 21的表现,毕竟 Project Loom 会带来大的变革。
但是 Java 21要等到2023年9月份才发布,还是建议拥抱变化,先把重点放在 Java 17上面把。