Java 17是一个长期支持(LTS)版本,于2021年9月14日达到普遍可用,请在这里下载Java 17。
Java 17有14个JEP项目:
伪随机数生成器,开关的模式匹配(预览),密封类(标准功能),外来函数和内存API(孵化器),动态反序列化过滤器。
1.JEP 306:恢复总是严格的浮点语义
该JEP适用于对数字敏感的程序,主要是科学用途;它再次将默认的浮点运算变得严格或Strictfp ,确保在每个平台上的浮点计算结果相同。
短暂的历史
- 在Java 1.2之前,所有的浮点计算都是严格的;而且在基于x87的硬件上会造成过热。
- 从Java 1.2开始,我们需要使用关键字
strictfp来启用严格浮点计算。默认的浮点计算从严格改为微妙不同的浮点计算(避免过热问题)。 - 现在,由于英特尔和AMD都支持SSE2(Streaming SIMD Extensions 2)扩展,它可以支持严格的JVM浮点运算,而不会出现过热问题,所以,以前(在Java 1.2之前)基于x87的硬件上的过热问题在今天的硬件上已经不复存在。
- Java 17将Java 1.2之前的严格浮点计算恢复为默认,这意味着关键字
strictfp现在是可选的。
进一步阅读
2.JEP 356:增强的伪随机数生成器
该JEP引入了一个名为RandomGenerator 的新接口,使未来的伪随机数生成器(PRNG)算法更容易实现或使用。
RandomGenerator.java
package java.util.random;
public interface RandomGenerator {
//...
}
下面的例子使用了新的Java 17RandomGeneratorFactory ,以获得著名的Xoshiro256PlusPlus PRNG算法来生成特定范围内的随机整数,0 - 10。
JEP356.java
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渲染管线
苹果在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 端口
苹果公司有一个长期计划,将其Mac从x64过渡到AArch64(例如,苹果M1处理器)。
该JEP将JDK移植到macOS的AArch64平台上运行。
进一步阅读
5.JEP 398:废弃Applet API的删除
Java Applet 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 ,切换到简单的封装,以便仍然可以访问内部的APIs。
这个JEP是上述Java 16 JEP 396的后继者,它多走了一步,删除了--illegal-access 选项,这意味着我们没有办法访问内部API,除了关键的内部API,如sun.misc.Unsafe 。
试试Java 17中的--illegal-access=warn 。
终端
java --illegal-access=warn
OpenJDK 64-Bit Server VM warning: Ignoring option --illegal-access=warn; support was removed in 17.0
进一步阅读
7.JEP 406: switch的模式匹配(预览)
这个JEP为switch 语句和表达式增加了模式匹配。由于这是一个预览功能,我们需要使用--enable-preview 选项来启用它。
7.1 if...else链
在Java 17之前,通常情况下,我们使用一连串的if...else 测试几种可能性。
JEP406.java
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中,我们可以这样重写上面的代码。
JEP406.java
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 模式匹配和空
现在我们可以直接测试switch 中的null 。
旧的代码
JEP406.java
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");
}
}
}
新代码
JEP406.java
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 细化开关中的模式
回顾下面的代码片断。为了测试Triangle t 和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:switch的模式匹配(预览)。
8.JEP 407:删除RMI激活
这个JEP删除了RMI激活或java.rmi.activation 包。
进一步阅读
9.JEP 409:密封类
Java 15的JEP 360和Java 16的JEP 397引入了[密封类(cr.openjdk.java.net/~briangoetz…
这个JEP最终将密封类作为Java 17的标准特性,与Java 16相比没有任何变化。
密封类和接口控制或限制谁可以成为一个子类型。
public sealed interface Command
permits LoginCommand, LogoutCommand, PluginCommand{
//...
}
进一步阅读
10.JEP 410:删除实验性AOT和JIT编译器
Java 9,JEP 295引入了Ahead-of-time编译(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废弃了安全管理器,以便删除。
SecurityManager.java
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:Foreign Function & Memory API(孵化器)
这个外来函数和内存API允许开发者访问JVM之外的代码(外来函数)、存储在JVM之外的数据(堆外数据),以及访问不由JVM管理的内存(外来内存)。
P.S 这是一个孵化功能;需要添加--add-modules jdk.incubator.foreign 来编译和运行Java代码。
历史
- Java 14JEP 370引入了国外内存访问API(孵化器)
- Java 15JEP 383介绍了国外内存访问API(第二孵化器)
- Java 16JEP 389引入了外国链接器API(孵化器)
- Java 16JEP 393引入国外内存访问API(第三孵化器)
- Java 17JEP 412引入了外国函数和内存API(孵化器)
请参考Java 16中以前的Foreign Linker API例子。
进一步阅读
13.JEP 414:向量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 下面的例子使用一个模式创建了一个自定义过滤器。
DdosExample.java
package com.mkyong.java17.jep415;
import java.io.Serializable;
public class DdosExample implements Serializable {
@Override
public String toString() {
return "running ddos...!";
}
}
JEP290.java
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 的类。
JComponentExample.java
package com.mkyong.java17.jep415;
import javax.swing.*;
import java.io.Serializable;
public class JComponentExample extends JComponent implements Serializable {
}
JEP290_B.java
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为ObjectInputFilter 接口增加了allowFilter 和rejectFilter ,以便更快地创建反序列化过滤器。
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引入了一个过滤器工厂的概念,即BinaryOperator ,以动态地选择不同的反序列化过滤器或特定的上下文。该工厂决定如何结合两个过滤器或替换过滤器。
下面是一个Java 17过滤器工厂的例子,它结合了两个反序列化过滤器。
JEP415_B.java
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 clonegithub.com/mkyong/core…
$ cd java-17