目录
1.Switch表达式新增匹配模式
类型模式匹配
守卫模式
2.文本块
3.增强的伪随机数生成器
4.密封类 sealed class
5.删除实验性的 AOT 和 JIT 编译器
6.弃用安全管理器和Applet API以进行删除
7.特定于上下文的反序列化过滤器
8.对 NullPointerExceptions的优化
9.增加Stream.toList()方法
10.本地变量类型推断
11.Records
使用 switch 表达式和语句的模式匹配以及对模式语言的扩展来增强 Java 编程语言。这个新特性允许使用新的模式,包括类型模式和守卫模式。类型模式能够在switch表达式中使用instanceof,守卫模式能够使用布尔表达式。
JDK16 instanceof 模式匹配
if (obj instanceof String s) {
// 直接使用 s拼接字符串
s += "heihei";
} else if (obj instanceof Integer i){
// 直接使用i进行整型逻辑运算
i += 1;
}
JDK17 switch 可直接用 instanceof 模式匹配选择(需要提前考虑 null 判断)
Object o;
switch (o) {
case null -> System.out.println("首先判断对象是否为空,走空指针逻辑等后续逻辑");
case String s -> System.out.println("判断是否为字符串,s:" + s);
case record p -> System.out.println("判断是否为Record类型: " + p.toString());
case int[] arr -> System.out.println("判断是否为数组,展示int数组的长度" + ia.length);
case Integer i -> System.out.println("判断是否为Intger对象,i:" + i);
case Student s -> System.out.println("判断是否为具体学生对象,student:" + s.toString());
case UserCommonService -> System.out.println("判断是否为普通用户实现类,然后走普通用户逻辑");
case UserVipService -> System.out.println("判断是否为vip用户实现类,然后走vip用户逻辑");
default -> System.out.println("Something else");
}
Object obj = "test";
switch (obj) {
case String s && s.length() > 0 -> s;
default -> "";
}
将冒号(:)替换为箭头(->),并且switch表达式默认不会失败,所以不需要break。
在Java17之前的版本里,如果我们需要定义一个字符串,比如一个JSON数据,基本都是如下方式定义:
public void lowVersion() {
String text = "{\n" +
" \"name\": \"小黑说Java\",\n" +
" \"age\": 18,\n" +
" \"address\": \"北京市西城区\"\n" +
"}";
System.out.println(text);
}
这种方式定义具有几个问题:
通过Java 17中的文本块语法,类似的字符串处理则会方便很多;通过三个双引号可以定义一个文本块,并且结束的三个双引号不能和开始的在同一行。
private void highVersion() {
String text = """
{
"name": "小黑说Java",
"age": 18,
"address": "北京市西城区"
}
""";
System.out.println(text);
}
JDK 17 之前,我们可以借助 Random
、ThreadLocalRandom
和SplittableRandom
来生成随机数。不过,这 3 个类都各有缺陷,且缺少常见的伪随机算法支持。
伪随机数生成器(pseudorandom number generator,PRNG),又称为确定性随机位生成器(deterministic random bit generator,DRBG),是用来生成接近于绝对随机数序列的数字序列的算法。一般来说,PRNG 会依赖于一个初始值,也称为种子,来生成对应的伪随机数序列。只要种子确定了,PRNG 所生成的随机数就是完全确定的,因此其生成的随机数序列并不是真正随机的。
就目前而言,PRNG 在众多应用都发挥着重要的作用,比如模拟(蒙特卡洛方法),电子竞技,密码应用。
//通过 RandomGeneratorFactory.of(“随机数生成算法”) 方法获得生成器
RandomGeneratorFactory l128X256MixRandom = RandomGeneratorFactory.of("L128X256MixRandom");
// 使用时间戳作为随机数种子
RandomGenerator randomGenerator = l128X256MixRandom.create(System.currentTimeMillis());
// 生成0-9随机数
System.out.println(randomGenerator.nextInt(10));
限制哪些其他类或接口可以扩展或实现它们。该提案的目标包括允许类或接口的作者控制由哪些代码负责实现它,提供一种比访问修饰符更具声明性的方式来限制超类的使用,并通过为模式的详尽分析提供基础来支持模式匹配的未来方向。
public abstract sealed class Furit permits Apple,Pear {
}
public non-sealed class Apple extends Furit {
}
public final class Pear extends Furit {
}
在定义Furit时通过关键字sealed声明为密封类,通过permits可以指定Apple,Pear类可以进行继承扩展。
指定的类permits必须位于超类附近:在同一个模块中(如果超类在命名模块中)或在同一个包中(如果超类在未命名模块中)。
指定的类permits必须具有规范名称,否则会报告编译时错误。这意味着匿名类和本地类不能成为密封类的子类型。
密封类对其允许的子类施加三个约束:
Java 17,删除实验性的提前 (AOT) 和即时 (JIT) 编译器,因为该编译器自推出以来很少使用,维护它所需的工作量很大。保留实验性的 Java 级 JVM 编译器接口 (JVMCI),以便开发人员可以继续使用外部构建的编译器版本进行 JIT 编译。
安全管理器可追溯到 Java 1.0,多年来,它一直不是保护客户端 Java 代码的主要方法,也很少用于保护服务器端代码。为了推动 Java 向前发展,Java 17 弃用安全管理器,以便与旧版 Applet API ( JEP 398open in new window ) 一起移除。
Applet API 用于编写在 Web 浏览器端运行的 Java 小程序,很多年前就已经被淘汰了,已经没有理由使用了。
允许应用程序使用 JVM 范围的过滤器工厂配置特定于上下文和动态选择的反序列化过滤器,该工厂用于为每个反序列化操作选择一个过滤器。
不可信数据的反序列化是一项具有内在风险的操作,因为在许多情况下传入数据流的内容是通过未知或未经身份验证的客户端获取的。
防止序列化攻击的关键是禁止任意类的实例被反序列化,从而直接或间接地阻止其方法的执行。
攻击者可以通过仔细构造流来运行任何恶意的类中的代码。如果对象构造涉及更改状态或触发其他操作的副作用,则应用程序对象、库对象和 Java 运行时的完整性可能会受到损害。
之前输出将显示NullPointerException发生的行号,但不知道哪个方法调用时产生的null,必须通过调试的方式找到。
Exception in thread "main" java.lang.NullPointerException
at com.heiz.java17.HelpfulNullPointerExceptionsDemo.main(HelpfulNullPointerExceptionsDemo.java:13)
在Java 17中,则会准确显示发生NPE的精确位置。
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.heiz.java17.Address.getCity()" because the return value of "com.heiz.java17.Person.getAddress()" is null
at com.heiz.java17.HelpfulNullPointerExceptionsDemo.main(HelpfulNullPointerExceptionsDemo.java:13)
如果需要将Stream转换成List,需要通过调用collect方法使用Collectors.toList(),代码非常冗长。
private static void oldStyle() {
Stream stringStream = Stream.of("a", "b", "c");
List stringList = stringStream.collect(Collectors.toList());
for(String s : stringList) {
System.out.println(s);
}
}
在Java 17中将会变得简单,可以直接调用toList()
。
private static void streamToList() {
Stream stringStream = Stream.of("a", "b", "c");
List stringList = stringStream.toList();
for(String s : stringList) {
System.out.println(s);
}
}
之前我们想定义定义局部变量时。我们需要在赋值的左侧提供显式类型,并在赋值的右边提供实现类型:
MyObject value = new MyObject();
在Java 10中,提供了本地变量类型推断的功能,可以通过var声明变量:
var value = new MyObject();
Java 14 中便包含了一个新特性:EP 359: Records
Records的目标是扩展Java语言语法,Records为声明类提供了一种紧凑的语法,用于创建一种类中是“字段,只是字段,除了字段什么都没有”的类。
通过对类做这样的声明,编译器可以通过自动创建所有方法并让所有字段参与hashCode()等方法。
record Person (String firstName, String lastName) {}
record 解决了使用类作为数据包装器的一个常见问题。纯数据类从几行代码显著地简化为一行代码。