Java 17 中的新功能
Java 17是一个长期支持 (LTS) 版本,已于 2021 年 9 月 14 日正式发布,请在此处下载 Java 17。
Java 17 有 14 个 JEP 项。
- 1. JEP 306:恢复始终严格的浮点语义
- 2. JEP 356:增强型伪随机数生成器
- 3. JEP 382:新的 macOS 渲染管线
- 4. JEP 391:macOS/AArch64 端口
- 5. JEP 398:弃用 Applet API 以进行删除
- 6. JEP 403:强封装JDK内部
- 7. JEP 406:开关模式匹配(预览)
- 8. JEP 407:删除 RMI 激活
- 9. JEP 409:密封类
- 10. JEP 410:删除实验性 AOT 和 JIT 编译器
- 11. JEP 411:弃用安全管理器以进行删除
- 12. JEP 412:外部函数和内存 API(孵化器)
- 13. JEP 414:Vector API(第二孵化器)
- 14. JEP 415:特定于上下文的反序列化过滤器
- 下载源代码
- 参考
Java 17 开发人员功能。
伪随机数生成器、开关模式匹配(预览)、密封类(标准功能)、外部函数和内存 API(孵化器)、动态反序列化过滤器。
1. JEP 306:恢复始终严格的浮点语义
此 JEP 用于数字敏感程序,主要是科学目的;它再次严格默认浮点运算,或Strictfp
确保每个平台上的浮点计算结果相同。
简短的历史
- 在 Java 1.2 之前,所有的浮点计算都是严格的;它导致基于 x87 的硬件过热。
- 从 Java 1.2 开始,我们需要关键字
strictfp
来启用严格的浮点计算。默认浮点计算已从严格的浮点计算更改为略有不同的浮点计算(避免过热问题)。 - 现在,由于 Intel 和 AMD 都支持SSE2(Streaming SIMD Extensions 2)扩展,可以支持严格的 JVM 浮点运算而不会过热,因此,之前(Java 1.2 之前)基于 x87 的硬件上的过热问题是在当今的硬件中不敬。
- Java 17 将 Java 1.2 之前的严格浮点计算恢复为默认值,这意味着关键字
strictfp
现在是可选的。
2. JEP 356:增强型伪随机数生成器
该 JEP 引入了一个新接口,称为RandomGenerator
使未来的伪随机数生成器 (PRNG)算法更易于实现或使用。
package java.util.random;
public interface RandomGenerator {
//...
}
下面的示例使用新的 Java 17RandomGeneratorFactory
来获得著名的Xoshiro256PlusPlus
PRNG 算法,以生成特定范围 0 – 10 内的随机整数。
package com.mkyong.java17.jep356;
import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;
public class JEP356 {
public static void main(String[] args) {
// legacy
// RandomGeneratorFactory.of("Random").create(42);
// default L32X64MixRandom
// RandomGenerator randomGenerator = RandomGeneratorFactory.getDefault().create();
// Passing the same seed to random, and then calling it will give you the same set of numbers
// for example, seed = 999
RandomGenerator randomGenerator = RandomGeneratorFactory.of("Xoshiro256PlusPlus").create(999);
System.out.println(randomGenerator.getClass());
int counter = 0;
while(counter<=10){
// 0-10
int result = randomGenerator.nextInt(11);
System.out.println(result);
counter++;
}
}
}
输出
class jdk.random.Xoshiro256PlusPlus
4
6
9
5
7
6
5
0
6
10
4
以下代码生成所有 Java 17 PRNG 算法。
RandomGeneratorFactory.all()
.map(fac -> fac.group()+ " : " +fac.name())
.sorted()
.forEach(System.out::println);
输出
LXM : L128X1024MixRandom
LXM : L128X128MixRandom
LXM : L128X256MixRandom
LXM : L32X64MixRandom
LXM : L64X1024MixRandom
LXM : L64X128MixRandom
LXM : L64X128StarStarRandom
LXM : L64X256MixRandom
Legacy : Random
Legacy : SecureRandom
Legacy : SplittableRandom
Xoroshiro : Xoroshiro128PlusPlus
Xoshiro : Xoshiro256PlusPlus
Java 17 还重构了遗留的随机类,如java.util.Random
,SplittableRandom
并SecureRandom
扩展新RandomGenerator
接口。
3. JEP 382:新的 macOS 渲染管线
Apple 在macOS 10.14 版本(2018 年 9 月)中弃用了 OpenGL 渲染库,转而支持新的Metal 框架以获得更好的性能。
此 JEP 将 macOS 的 Java 2D(如 Swing GUI)内部渲染管道从 Apple OpenGL API 更改为 Apple Metal API;这是一个内部变化;没有新的 Java 2D API,也没有更改任何现有的 API。
4. JEP 391:macOS/AArch64 端口
Apple 有一个将其 Mac 从 x64 过渡到 AArch64(例如 Apple M1 处理器)的长期计划。
此 JEP 端口 JDK 可在 macOS 上的 AArch64 平台上运行。
5. JEP 398:弃用 Applet API 以进行删除
在Java小程序API,因为大多数的网页浏览器的移除了用于Java的浏览器插件的支持是无关紧要的。
Java 9 弃用了 Applet API。
@Deprecated(since = "9")
public class Applet extends Panel {
//...
}
此 JEP 将 Applet API 标记为要删除。
@Deprecated(since = "9", forRemoval = true)
@SuppressWarnings("removal")
public class Applet extends Panel {
//...
}
6. JEP 403:强封装JDK内部
许多第三方库、框架和工具正在访问 JDK 的内部 API 和包。Java 16、JEP 396默认进行了强封装(我们不允许轻易访问内部 API)。但是,我们仍然可以使用--illegal-access
切换到简单封装来仍然访问内部 API。
这个 JEP 是上述 Java 16 JEP 396 的后继者,它通过删除--illegal-access
选项又进了一步,这意味着我们无法访问内部 API,除了关键的内部 API,如sun.misc.Unsafe
.
--illegal-access=warn
在 Java 17 中尝试。
java --illegal-access=warn
OpenJDK 64-Bit Server VM warning: Ignoring option --illegal-access=warn; support was removed in 17.0
进一步阅读
7. JEP 406:开关模式匹配(预览)
此 JEP 为switch
语句和表达式添加了模式匹配。由于这是一个预览功能,我们需要使用--enable-preview
选项来启用它。
7.1 if...else 链
在 Java 17 之前,我们通常使用一系列if...else
测试来测试几种可能性。
package com.mkyong.java17.jep406;
public class JEP406 {
public static void main(String[] args) {
System.out.println(formatter("Java 17"));
System.out.println(formatter(17));
}
static String formatter(Object o) {
String formatted = "unknown";
if (o instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
formatted = String.format("double %f", d);
} else if (o instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
}
在 Java 17 中,我们可以像这样重写上面的代码:
package com.mkyong.java17.jep406;
public class JEP406 {
public static void main(String[] args) {
System.out.println(formatterJava17("Java 17"));
System.out.println(formatterJava17(17));
}
static String formatterJava17(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();
};
}
}
7.2 模式匹配和空值
现在,我们可以测试null
中switch
直接。
旧代码。
package com.mkyong.java17.jep406;
public class JEP406 {
public static void main(String[] args) {
testString("Java 16"); // Ok
testString("Java 11"); // LTS
testString(""); // Ok
testString(null); // Unknown!
}
static void testString(String s) {
if (s == null) {
System.out.println("Unknown!");
return;
}
switch (s) {
case "Java 11", "Java 17" -> System.out.println("LTS");
default -> System.out.println("Ok");
}
}
}
新代码。
package com.mkyong.java17.jep406;
public class JEP406 {
public static void main(String[] args) {
testStringJava17("Java 16"); // Ok
testStringJava17("Java 11"); // LTS
testStringJava17(""); // Ok
testStringJava17(null); // Unknown!
}
static void testStringJava17(String s) {
switch (s) {
case null -> System.out.println("Unknown!");
case "Java 11", "Java 17" -> System.out.println("LTS");
default -> System.out.println("Ok");
}
}
}
7.3 在 switch 中细化模式
查看以下代码片段。为了测试Triangle t
and t.calculateArea()
,我们需要创建一个额外的if
条件。
class Shape {}
class Rectangle extends Shape {}
class Triangle extends Shape {
int calculateArea(){
//...
} }
static void testTriangle(Shape s) {
switch (s) {
case null:
break;
case Triangle t:
if (t.calculateArea() > 100) {
System.out.println("Large triangle");
break;
}else{
System.out.println("Triangle");
}
default:
System.out.println("Unknown!");
}
}
Java 17 允许所谓的重新定义模式或guarded patterns
如下所示:
static void testTriangle2(Shape s) {
switch (s) {
case null ->
{}
case Triangle t && (t.calculateArea() > 100) ->
System.out.println("Large triangle");
case Triangle t ->
System.out.println("Triangle");
default ->
System.out.println("Unknown!");
}
}
进一步阅读
有关更多示例和解释,请访问此JEP 406:开关模式匹配(预览版)
8. JEP 407:删除 RMI 激活
此 JEP 删除了 RMI 激活或java.rmi.activation
包。
进一步阅读
9. JEP 409:密封类
Java 15、JEP 360和 Java 16、JEP 397引入了 [密封类 (https://cr.openjdk.java.net/~briangoetz/amber/datum.html) 作为预览功能。
该 JEP 将密封类最终确定为 Java 17 中的标准特性,与 Java 16 没有任何变化。
密封的类和接口控制或限制谁可以是子类型。
public sealed interface Command
permits LoginCommand, LogoutCommand, PluginCommand{
//...
}
进一步阅读
10. JEP 410:删除实验性 AOT 和 JIT 编译器
Java 9、JEP 295引入了提前编译(jaotc
工具)作为实验性功能。在 Java 10 之后,JEP 317再次提出它作为实验性 JIT 编译器。
然而,这个特性自从被引入以来几乎没有什么用,并且需要付出大量的努力来维护它,所以这个 JEP 删除了实验性的基于 Java 的提前 (AOT)和即时 (JIT)编译器
删除了以下 AOT 包、类、工具和代码:
jdk.aot
— jaotc 工具jdk.internal.vm.compiler
— Graal 编译器jdk.internal.vm.compiler.management
- Graal 的 MBeansrc/hotspot/share/aot
— 转储和加载 AOT 代码- 由保护的附加代码
#if INCLUDE_AOT
11. JEP 411:弃用安全管理器以进行删除
Java 1.0 引入了安全管理器来保护客户端 Java 代码,现在已经不相关了。
此 JEP 弃用要删除的安全管理器。
package java.lang;
* @since 1.0
* @deprecated The Security Manager is deprecated and subject to removal in a
* future release. There is no replacement for the Security Manager.
* See <a href="https://openjdk.java.net/jeps/411">JEP 411</a> for
* discussion and alternatives.
*/
@Deprecated(since="17", forRemoval=true)
public class SecurityManager {
//...
}
进一步阅读
12. JEP 412:外部函数和内存 API(孵化器)
这个Foreign Function & Memory API允许开发者访问JVM外的代码(外来函数)、存储在JVM外的数据(堆外数据),以及访问非JVM管理的内存(外来内存)。
PS 这是一个孵化功能;需要添加--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 16 中以前的外部链接器 API 示例。
13. JEP 414:Vector API(第二孵化器)
Java 16、JEP 414引入了新的 Vector API 作为孵化 API。
此 JEP 改进了 Vector API 性能和其他增强功能,例如对字符的支持操作、字节向量与布尔数组之间的相互转换等。
14. JEP 415:特定于上下文的反序列化过滤器
在 Java 中,反序列化不受信任的数据是危险的,请阅读OWASP – 不受信任数据的反序列化和Brian Goetz – Towards Better Serialization。
Java 9、JEP 290引入了序列化过滤来帮助防止反序列化漏洞。
14.1 以下示例使用模式创建自定义过滤器。
package com.mkyong.java17.jep415;
import java.io.Serializable;
public class DdosExample implements Serializable {
@Override
public String toString() {
return "running ddos...!";
}
}
package com.mkyong.java17.jep415;
import java.io.*;
public class JEP290 {
public static void main(String[] args) throws IOException {
byte[] bytes = convertObjectToStream(new DdosExample());
InputStream is = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(is);
// Setting a Custom Filter Using a Pattern
// need full package path
// the maximum number of bytes in the input stream = 1024
// allows classes in com.mkyong.java17.jep415.*
// allows classes in the java.base module
// rejects all other classes !*
ObjectInputFilter filter1 =
ObjectInputFilter.Config.createFilter(
"maxbytes=1024;com.mkyong.java17.jep415.*;java.base/*;!*");
ois.setObjectInputFilter(filter1);
try {
Object obj = ois.readObject();
System.out.println("Read obj: " + obj);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private static byte[] convertObjectToStream(Object obj) {
ByteArrayOutputStream boas = new ByteArrayOutputStream();
try (ObjectOutputStream ois = new ObjectOutputStream(boas)) {
ois.writeObject(obj);
return boas.toByteArray();
} catch (IOException ioe) {
ioe.printStackTrace();
}
throw new RuntimeException();
}
}
输出
Read obj: running ddos...!
下面的示例将拒绝包中的所有类com.mkyong.java17.jep415.*
:
byte[] bytes = convertObjectToStream(new DdosExample());
ObjectInputFilter filter1 =
ObjectInputFilter.Config.createFilter(
"!com.mkyong.java17.jep415.*;java.base/*;!*");
重新运行它;这一次,我们将无法反序列化对象。
Exception in thread "main" java.io.InvalidClassException: filter status: REJECTED
at java.base/java.io.ObjectInputStream.filterCheck(ObjectInputStream.java:1412)
14.2 下面的例子创建了一个反序列化过滤器来拒绝所有扩展JComponent
.
package com.mkyong.java17.jep415;
import javax.swing.*;
import java.io.Serializable;
public class JComponentExample extends JComponent implements Serializable {
}
package com.mkyong.java17.jep415;
import javax.swing.*;
import java.io.*;
public class JEP290_B {
public static void main(String[] args) throws IOException {
byte[] bytes = convertObjectToStream(new JComponentExample());
InputStream is = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(is);
ois.setObjectInputFilter(createObjectFilter());
try {
Object obj = ois.readObject();
System.out.println("Read obj: " + obj);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// reject all JComponent classes
private static ObjectInputFilter createObjectFilter() {
return filterInfo -> {
Class<?> clazz = filterInfo.serialClass();
if (clazz != null) {
return (JComponent.class.isAssignableFrom(clazz))
? ObjectInputFilter.Status.REJECTED
: ObjectInputFilter.Status.ALLOWED;
}
return ObjectInputFilter.Status.UNDECIDED;
};
}
private static byte[] convertObjectToStream(Object obj) {
ByteArrayOutputStream boas = new ByteArrayOutputStream();
try (ObjectOutputStream ois = new ObjectOutputStream(boas)) {
ois.writeObject(obj);
return boas.toByteArray();
} catch (IOException ioe) {
ioe.printStackTrace();
}
throw new RuntimeException();
}
}
输出
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)
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2209)
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1742)
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:514)
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:472)
at com.mkyong.java17.jep415.JEP290_B.main(JEP290_B.java:17)
14.3 Java的17加allowFilter
和rejectFilter
的ObjectInputFilter
界面来创建反序列化过滤器更快。
allowFilter(Predicate<Class<?>>, ObjectInputFilter.Status)
rejectFilter(Predicate<Class<?>>, ObjectInputFilter.Status)
对于 14.2 中的上述示例,现在我们可以重构如下代码:
// Java 9
private static ObjectInputFilter createObjectFilter() {
return filterInfo -> {
Class<?> clazz = filterInfo.serialClass();
if (clazz != null) {
return (JComponent.class.isAssignableFrom(clazz))
? ObjectInputFilter.Status.REJECTED
: ObjectInputFilter.Status.ALLOWED;
}
return ObjectInputFilter.Status.UNDECIDED;
};
}
// Java 17
// reject all JComponent classes
ObjectInputFilter jComponentFilter = ObjectInputFilter.rejectFilter(
JComponent.class::isAssignableFrom,
ObjectInputFilter.Status.UNDECIDED);
ois.setObjectInputFilter(jComponentFilter);
14.4 回到 Java 17,此 JEP 415 引入了过滤器工厂的概念 a BinaryOperator
,以动态或特定于上下文选择不同的反序列化过滤器。工厂决定如何组合两个过滤器或更换过滤器。
下面是结合两个反序列化过滤器的 Java 17 过滤器工厂示例。
package com.mkyong.java17.jep415;
import java.io.*;
import java.util.function.BinaryOperator;
public class JEP415_B {
static class PrintFilterFactory implements BinaryOperator<ObjectInputFilter> {
@Override
public ObjectInputFilter apply(
ObjectInputFilter currentFilter, ObjectInputFilter nextFilter) {
System.out.println("Current filter: " + currentFilter);
System.out.println("Requested filter: " + nextFilter);
// Returns a filter that merges the status of a filter and another filter
return ObjectInputFilter.merge(nextFilter, currentFilter);
// some logic and return other filters
// reject all JComponent classes
/*return filterInfo -> {
Class<?> clazz = filterInfo.serialClass();
if (clazz != null) {
if(JComponent.class.isAssignableFrom(clazz)){
return ObjectInputFilter.Status.REJECTED;
}
}
return ObjectInputFilter.Status.ALLOWED;
};*/
}
}
public static void main(String[] args) throws IOException {
// Set a filter factory
PrintFilterFactory filterFactory = new PrintFilterFactory();
ObjectInputFilter.Config.setSerialFilterFactory(filterFactory);
// create a maxdepth and package filter
ObjectInputFilter filter1 =
ObjectInputFilter.Config.createFilter(
"com.mkyong.java17.jep415.*;java.base/*;!*");
ObjectInputFilter.Config.setSerialFilter(filter1);
// Create a filter to allow String.class only
ObjectInputFilter intFilter = ObjectInputFilter.allowFilter(
cl -> cl.equals(String.class), ObjectInputFilter.Status.REJECTED);
// if pass anything other than String.class, hits filter status: REJECTED
//byte[] byteStream =convertObjectToStream(99);
// Create input stream
byte[] byteStream =convertObjectToStream("hello");
InputStream is = new ByteArrayInputStream(byteStream);
ObjectInputStream ois = new ObjectInputStream(is);
ois.setObjectInputFilter(intFilter);
try {
Object obj = ois.readObject();
System.out.println("Read obj: " + obj);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private static byte[] convertObjectToStream(Object obj) {
ByteArrayOutputStream boas = new ByteArrayOutputStream();
try (ObjectOutputStream ois = new ObjectOutputStream(boas)) {
ois.writeObject(obj);
return boas.toByteArray();
} catch (IOException ioe) {
ioe.printStackTrace();
}
throw new RuntimeException();
}
}
输出
Current filter: null
Requested filter: com.mkyong.java17.jep415.*;java.base/*;!*
Current filter: com.mkyong.java17.jep415.*;java.base/*;!*
Requested filter: predicate(
com.mkyong.java17.jep415.JEP415_B$$Lambda$22/0x0000000800c01460@15aeb7ab,
ifTrue: ALLOWED, ifFalse:REJECTED)
Read obj: hello
下载源代码
$ git clone https://github.com/mkyong/core-java
$ cd java-17