安全导航运算符
安全导航操作符(?.)用于避免出现 NullPointerException,它源自 Groovy 语言。
通常情况下,当你持有某个对象的引用时,在访问该对象的方法或属性之前,可能需要先验证它是否为 null。为了避免这种情况,安全导航操作符会在特定的空值安全操作中返回 null,而不是抛出异常。
|
当安全导航操作符在复合表达式中的特定空安全操作上求值为 详情请参见 复合表达式中的空安全操作。 |
安全的属性和方法访问
以下示例演示了如何使用安全导航操作符进行属性访问(?.)。
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
// evaluates to "Smiljan"
String city = parser.parseExpression("placeOfBirth?.city") (1)
.getValue(context, tesla, String.class);
tesla.setPlaceOfBirth(null);
// evaluates to null - does not throw NullPointerException
city = parser.parseExpression("placeOfBirth?.city") (2)
.getValue(context, tesla, String.class);
| 1 | 在非空的 placeOfBirth 属性上使用安全导航操作符 |
| 2 | 在空的 placeOfBirth 属性上使用安全导航操作符 |
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()
val tesla = Inventor("Nikola Tesla", "Serbian")
tesla.setPlaceOfBirth(PlaceOfBirth("Smiljan"))
// evaluates to "Smiljan"
var city = parser.parseExpression("placeOfBirth?.city") (1)
.getValue(context, tesla, String::class.java)
tesla.setPlaceOfBirth(null)
// evaluates to null - does not throw NullPointerException
city = parser.parseExpression("placeOfBirth?.city") (2)
.getValue(context, tesla, String::class.java)
| 1 | 在非空的 placeOfBirth 属性上使用安全导航操作符 |
| 2 | 在空的 placeOfBirth 属性上使用安全导航操作符 |
|
安全导航操作符也适用于对象上的方法调用。 例如,如果上下文中未配置 |
安全的索引访问
自 Spring Framework 6.2 起,Spring 表达式语言支持对以下类型结构进行安全导航索引。
以下示例展示了如何使用安全导航运算符来索引列表(?.[])中的元素。
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
EvaluationContext context = new StandardEvaluationContext(society);
// evaluates to Inventor("Nikola Tesla")
Inventor inventor = parser.parseExpression("members?.[0]") (1)
.getValue(context, Inventor.class);
society.members = null;
// evaluates to null - does not throw an exception
inventor = parser.parseExpression("members?.[0]") (2)
.getValue(context, Inventor.class);
| 1 | 在非空members列表上使用空安全索引操作符 |
| 2 | 在空的 members 列表上使用空安全索引操作符 |
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)
// evaluates to Inventor("Nikola Tesla")
var inventor = parser.parseExpression("members?.[0]") (1)
.getValue(context, Inventor::class.java)
society.members = null
// evaluates to null - does not throw an exception
inventor = parser.parseExpression("members?.[0]") (2)
.getValue(context, Inventor::class.java)
| 1 | 在非空members列表上使用空安全索引操作符 |
| 2 | 在空的 members 列表上使用空安全索引操作符 |
安全的集合选择与投影
-
null安全的选择:
?.? -
空安全选择第一个:
?.^ -
空安全选择最后:
?.$ -
null安全的投影:
?.!
以下示例演示了如何对集合选择使用安全导航操作符(?.?)。
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression = "members?.?[nationality == 'Serbian']"; (1)
// evaluates to [Inventor("Nikola Tesla")]
List<Inventor> list = (List<Inventor>) parser.parseExpression(expression)
.getValue(context);
society.members = null;
// evaluates to null - does not throw a NullPointerException
list = (List<Inventor>) parser.parseExpression(expression)
.getValue(context);
| 1 | 在可能为 null 的 members 列表上使用空安全选择操作符 |
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)
val expression = "members?.?[nationality == 'Serbian']" (1)
// evaluates to [Inventor("Nikola Tesla")]
var list = parser.parseExpression(expression)
.getValue(context) as List<Inventor>
society.members = null
// evaluates to null - does not throw a NullPointerException
list = parser.parseExpression(expression)
.getValue(context) as List<Inventor>
| 1 | 在可能为 null 的 members 列表上使用空安全选择操作符 |
以下示例演示了如何对集合使用“空安全选择第一个”操作符(?.^)。
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression =
"members?.^[nationality == 'Serbian' || nationality == 'Idvor']"; (1)
// evaluates to Inventor("Nikola Tesla")
Inventor inventor = parser.parseExpression(expression)
.getValue(context, Inventor.class);
society.members = null;
// evaluates to null - does not throw a NullPointerException
inventor = parser.parseExpression(expression)
.getValue(context, Inventor.class);
| 1 | 在可能为 null 的 members 列表上使用“空安全选择第一个”操作符 |
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)
val expression =
"members?.^[nationality == 'Serbian' || nationality == 'Idvor']" (1)
// evaluates to Inventor("Nikola Tesla")
var inventor = parser.parseExpression(expression)
.getValue(context, Inventor::class.java)
society.members = null
// evaluates to null - does not throw a NullPointerException
inventor = parser.parseExpression(expression)
.getValue(context, Inventor::class.java)
| 1 | 在可能为 null 的 members 列表上使用“空安全选择第一个”操作符 |
以下示例演示了如何对集合使用“空安全选择最后一个”操作符(?.$)。
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression =
"members?.$[nationality == 'Serbian' || nationality == 'Idvor']"; (1)
// evaluates to Inventor("Pupin")
Inventor inventor = parser.parseExpression(expression)
.getValue(context, Inventor.class);
society.members = null;
// evaluates to null - does not throw a NullPointerException
inventor = parser.parseExpression(expression)
.getValue(context, Inventor.class);
| 1 | 在可能为 null 的 members 列表上使用“空安全选择最后一个”操作符 |
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)
val expression =
"members?.$[nationality == 'Serbian' || nationality == 'Idvor']" (1)
// evaluates to Inventor("Pupin")
var inventor = parser.parseExpression(expression)
.getValue(context, Inventor::class.java)
society.members = null
// evaluates to null - does not throw a NullPointerException
inventor = parser.parseExpression(expression)
.getValue(context, Inventor::class.java)
| 1 | 在可能为 null 的 members 列表上使用“空安全选择最后一个”操作符 |
以下示例演示了如何对集合投影使用安全导航操作符(?.!)。
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
// evaluates to ["Smiljan", "Idvor"]
List placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") (1)
.getValue(context, List.class);
society.members = null;
// evaluates to null - does not throw a NullPointerException
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") (2)
.getValue(context, List.class);
| 1 | 在非空的 members 列表上使用空安全投影操作符 |
| 2 | 在空的 members 列表上使用空安全投影操作符 |
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)
// evaluates to ["Smiljan", "Idvor"]
var placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") (1)
.getValue(context, List::class.java)
society.members = null
// evaluates to null - does not throw a NullPointerException
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") (2)
.getValue(context, List::class.java)
| 1 | 在非空的 members 列表上使用空安全投影操作符 |
| 2 | 在空的 members 列表上使用空安全投影操作符 |
对Optional进行空安全操作
自Spring Framework 7.0起,对
Optional 实例支持空安全操作,并具有透明的拆包语义。
具体而言,当空安全运算符应用于一个empty的Optional时,它将被视为Optional如同是null,随后的操作将评估为null。然而,如果空安全运算符应用于非空的Optional,随后的操作将应用于Optional中包含的对象,从而有效地展开Optional。
例如,如果 user 是类型 Optional<User>,那么表达式 user?.name 将会
评估为 null,当 user 为 null 或者一个empty的 Optional 时;否则
将评估为 user 的 name,实际上对于属性或字段访问分别user.get().getName() 或
user.get().name。
|
在 |
同样地,如果names的类型为Optional<List<String>>,则表达式
names?.?[#this.length > 5]将在names为null或一个empty
Optional的情况下评估为null,否则将评估为包含长度
大于5的名称的序列,从而有效地
names.get().stream().filter(s → s.length() > 5).toList()。
本章节中先前提到的所有空值安全操作符都遵循相同的语义。
为了了解更多详细信息和示例,请参考以下运算符的javadoc文档。
复合表达式中的空安全操作
正如本节开头所述,当安全导航操作符在复合表达式中的特定空安全操作上求值为 null 时,
复合表达式的其余部分仍将被求值。这意味着必须在整个复合表达式中应用安全导航操作符,
以避免出现任何不必要的 NullPointerException。
给定表达式 #person?.address.city,如果 #person 为 null,安全导航运算符(?.)可确保在尝试访问 #person 的 address 属性时不会抛出异常。然而,由于 #person?.address 计算结果为 null,在尝试访问 null 的 city 属性时将抛出一个 NullPointerException。为解决此问题,你可以在整个复合表达式中应用空安全导航,如 #person?.address?.city 所示。如果 #person 或 #person?.address 中任意一个计算结果为 null,该表达式将安全地计算为 null。
以下示例演示了如何在集合上使用“空安全选择第一个”操作符(?.^)并结合复合表达式中的空安全属性访问(?.)。如果 members 为 null,则“空安全选择第一个”操作符(members?.^[nationality == 'Serbian'])的结果将计算为 null,而进一步使用安全导航操作符(?.name)可确保整个复合表达式计算结果为 null,而不是抛出异常。
-
Java
-
Kotlin
ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression = "members?.^[nationality == 'Serbian']?.name"; (1)
// evaluates to "Nikola Tesla"
String name = parser.parseExpression(expression)
.getValue(context, String.class);
society.members = null;
// evaluates to null - does not throw a NullPointerException
name = parser.parseExpression(expression)
.getValue(context, String.class);
| 1 | 在复合表达式中使用“空安全选择第一个”和空安全属性访问操作符。 |
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)
val expression = "members?.^[nationality == 'Serbian']?.name" (1)
// evaluates to "Nikola Tesla"
String name = parser.parseExpression(expression)
.getValue(context, String::class.java)
society.members = null
// evaluates to null - does not throw a NullPointerException
name = parser.parseExpression(expression)
.getValue(context, String::class.java)
| 1 | 在复合表达式中使用“空安全选择第一个”和空安全属性访问操作符。 |