1. 前言
随着时代的进步JDK的版本也在不断更新,目前像是SpringBoot等框架支持的最低稳定版本都是JDK17了,早几年使用的JDK1.8现在也慢慢推出历史舞台,借此机会学习学习有哪些新特性,记录一下个人觉得可用性高的😁大家也可以上OpenJDK官网学习更全面的知识!!
2. 新特性
2.1 instanceof 关键字的优化
可以看到下面的代码中旧版本和新版本的区别就是少了一个强制类型转换,这段代码我相信大部分人都写🤮了吧,判断是否是某个类型 - 如果是就强转 - 然后才能使用该变量; 那么在新版本中就不需要中间的强转操作啦,可以直接写成o instanceof K s
(底层帮你强转好了)只要在判断的if代码段内,s 都为 K 的类型!!事实上这个优化在16版本就推出了(确实好用)
// 旧版本
if (o instanceof String) {
String s = (String)o;
... use s ...
}
// 新版本
if (o instanceof String s) {
... use s ...
}
2.2 对switch匹配的优化
旧版本里面switch只能匹配一些基本类型(int, long, …),枚举类型和String,这极大了限制了我们开发的选择,遇到复杂的包装类型我们就只能使用冗余的 if-else 极大增加代码的时间复杂度。
下面的例子分别展示了新旧版本的写法,我们可以看到下面新版本的switch明显更简介高效,且可匹配任意类型加两个模式(保护模式和括号模式),不再限制于上面提到的那几个!在代码细节上使用 -> 代替 :
使得整体看着更舒服
引自原文 In this case we are more likely to be able to perform the dispatch in O(1) time.
// 旧版本
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;
}
// 新版本
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();
};
}
2.2.1 多匹配
从下面代码示例中我们就可以了解到什么是多匹配,在旧版本中不同的case执行相同的内容需要分开写,而在新版本中可以用 ,
进行分割,写在一个case里面!!
switch (s) {
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
注意:当匹配模式为子父类的情况下,子类的case必须写在父类的case之前!!
2.2.2 null的匹配
既然上面说到了可以匹配所有的内容,那么null
自然也就不在话下,在新版本中,null无需作为外部判断,可以作为switch中的一个case来进行匹配:
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");
}
}
当然 2.2.1 提到的多匹配也可以用在 null
上,如下例子:
static void testStringOrNull(Object o) {
switch (o) {
case null, String s -> System.out.println("String: " + s);
}
}
2.2.3 细化switch中的模式
2.2.3.1 保护模式
下面的例子中我们可以看到,旧版本的:
我们需要判断t的面积只能进去case后做一个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;
}
default:
System.out.println("A shape, possibly a small triangle");
}
}
而在新版本中,同样代码不同的写法,我们使用 ->
来代替,并且case中可以支持保护模式
p && e 就是保护模式的形式, p 是模式,e 是布尔表达式。p 中的任何模式变量声明的范围都包括表达式 e。如下代码中,t 匹配可以强制转换为 Triangle 的类型,使得计算的面积要大于100。
static void testTriangle(Shape s) {
switch (s) {
case Triangle t && (t.calculateArea() > 100) ->
System.out.println("Large triangle");
default ->
System.out.println("A shape, possibly a small triangle");
}
}
2.2.3.2 括号模式
这种模式允许将模式放在括号中,从而在不需要额外判断的情况下直接进行模式匹配。在下面这个例子中,我们使用了括号模式来匹配number的值。每个case语句都包含一个括号,其中包含了要匹配的模式。如果模式匹配成功,就会执行相应的代码块。
注意,在括号模式中,不需要使用break语句来终止switch语句的执行。这是因为在Java 12及更高版本中,switch语句已经默认支持了这种特性。
int number = 3;
String result;
switch (number) {
case (1) -> result = "One";
case (2) -> result = "Two";
case (3) -> result = "Three";
default -> result = "Unknown";
}
System.out.println(result); // 输出: Three
2.3 密封类 Sealed classes
通过sealed
修饰符来描述某个类为密封类,同时使用permits
关键字来制定可以继承或实现该类的类型有哪些。注意sealed可以修饰的是类(class)或者接口(interface),所以permits关键字的位置应该在extends或者implements之后。
如下代码指明pigService为继承了animalService的密封接口,但可以被pigServiceImpl来实现
public sealed interface pigService extends animalService permits pigServiceImpl {
void doSomething();
}
对于密封类来说,其子类如果仍然是密封的类,说明由下游调用方继续提供密封保障。而如果是final最终态类
的话,则指定类已经形成完全密封,所以满足密封保障。而当使用non-sealed关键字
对类进行显式的声明其不进行密封,这种情况下由下游调用方承担打破密封的风险。即是从密封性的角度上来说,sealed子类传递了密封性;final的子类确认密封性;non-sealed显式放弃密封性。
所以说Java提出的密封类实现了两点功能:
- 对无序扩展的限制
- 在需要进行扩展的时候,仍然可以基于其一种扩展能力,但是风险并不通过提供能力的组件承担,而是用进行声明的用户进行承担。
注意对类的继承,子类必须为permits关键字声明类的直接实现类。且不支持匿名类函数式接口。
2.4 Context-Specific Deserialization Filters(上下文特定的反序列化过滤器)
JDK 17 中引入的一个特性,用于增强 Java 应用程序对反序列化过程的控制和安全性。 在 Java 中,反序列化是将对象从字节序列还原为内存中的对象的过程。然而,反序列化操作可能存在安全风险,因为恶意序列化数据可以触发未经授权的代码执行或对象创建。 Context-Specific Deserialization Filters 允许开发人员定义特定于上下文的反序列化过滤器,以限制哪些类可以被反序列化。开发人员可以在特定的上下文中设置反序列化过滤器,例如在应用程序的安全管理器中、在 RMI 远程调用中、在对象输入流的特定实例上等。 通过定义上下文特定的反序列化过滤器,开发人员可以实现以下目标:
- 类过滤:可以定义白名单或黑名单,只允许或禁止特定的类进行反序列化。这有助于限制潜在的安全风险和不受信任的类的反序列化。
- 内容检查:可以对反序列化的对象进行更严格的验证和检查,以确保数据的完整性和合法性。
- 安全策略实施:可以通过上下文特定的反序列化过滤器实施特定的安全策略,以满足应用程序的需求和要求。
通过使用 Context-Specific Deserialization Filters,开发人员可以在反序列化过程中更细粒度地控制和验证对象的创建和内容。这有助于提高应用程序的安全性,减少潜在的安全漏洞和攻击面。