安全导航操作符
安全导航操作符 (?.) 用于避免 NullPointerException,它来源于
Groovy
语言。通常,当你有一个对象引用时,你可能需要在访问该对象的方法或属性之前验证它是否为 null。为了避免这种情况,安全导航操作符会针对特定的空安全操作返回 null,而不是抛出异常。
当安全导航操作符在复合表达式中对某个空安全操作求值为 null 时,复合表达式的其余部分仍将继续求值。
有关详细信息,请参阅 复合表达式中的空安全操作。
安全属性和方法访问
以下示例展示了如何将安全导航操作符用于属性访问 (?.)。
- Java
-
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") [id="CO1-1"][id="CO1-1"][id="CO1-1"](1) .getValue(context, tesla, String.class); tesla.setPlaceOfBirth(null); // evaluates to null - does not throw NullPointerException city = parser.parseExpression("placeOfBirth?.city") [id="CO1-2"][id="CO1-2"][id="CO1-2"](2) .getValue(context, tesla, String.class);
<1> 在非空 `placeOfBirth` 属性上使用安全导航操作符 <1> 在空 `placeOfBirth` 属性上使用安全导航操作符
- Kotlin
-
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") [id="CO2-1"][id="CO1-3"][id="CO2-1"](1) .getValue(context, tesla, String::class.java) tesla.setPlaceOfBirth(null) // evaluates to null - does not throw NullPointerException city = parser.parseExpression("placeOfBirth?.city") [id="CO2-2"][id="CO1-4"][id="CO2-2"](2) .getValue(context, tesla, String::class.java)
<1> 在非空 `placeOfBirth` 属性上使用安全导航操作符 <1> 在空 `placeOfBirth` 属性上使用安全导航操作符
|
安全导航操作符也适用于对象上的方法调用。
例如,如果 |
安全索引访问
自 Spring Framework 6.2 起,Spring 表达式语言支持对以下类型的结构进行安全索引访问。
以下示例展示了如何将安全导航操作符用于列表索引 (?.[])。
- Java
-
ExpressionParser parser = new SpelExpressionParser(); IEEE society = new IEEE(); EvaluationContext context = new StandardEvaluationContext(society); // evaluates to Inventor("Nikola Tesla") Inventor inventor = parser.parseExpression("members?.[0]") [id="CO3-1"][id="CO1-5"][id="CO3-1"](1) .getValue(context, Inventor.class); society.members = null; // evaluates to null - does not throw an exception inventor = parser.parseExpression("members?.[0]") [id="CO3-2"][id="CO1-6"][id="CO3-2"](2) .getValue(context, Inventor.class);
<1> 在非空 `members` 列表上使用空安全索引操作符 <1> 在空 `members` 列表上使用空安全索引操作符
- Kotlin
-
val parser = SpelExpressionParser() val society = IEEE() val context = StandardEvaluationContext(society) // evaluates to Inventor("Nikola Tesla") var inventor = parser.parseExpression("members?.[0]") [id="CO4-1"][id="CO1-7"][id="CO4-1"](1) .getValue(context, Inventor::class.java) society.members = null // evaluates to null - does not throw an exception inventor = parser.parseExpression("members?.[0]") [id="CO4-2"][id="CO1-8"][id="CO4-2"](2) .getValue(context, Inventor::class.java)
<1> 在非空 `members` 列表上使用空安全索引操作符 <1> 在空 `members` 列表上使用空安全索引操作符
安全集合选择和投影
-
空安全选择:
?.? -
空安全选择第一个:
?.^ -
空安全选择最后一个:
?.$ -
空安全投影:
?.!
以下示例展示了如何将安全导航操作符用于集合选择 (?.?)。
- Java
-
ExpressionParser parser = new SpelExpressionParser(); IEEE society = new IEEE(); StandardEvaluationContext context = new StandardEvaluationContext(society); String expression = "members?.?[nationality == 'Serbian']"; [id="CO5-1"][id="CO1-9"][id="CO5-1"](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` 列表上使用空安全选择操作符
- Kotlin
-
val parser = SpelExpressionParser() val society = IEEE() val context = StandardEvaluationContext(society) val expression = "members?.?[nationality == 'Serbian']" [id="CO6-1"][id="CO1-10"][id="CO6-1"](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
-
ExpressionParser parser = new SpelExpressionParser(); IEEE society = new IEEE(); StandardEvaluationContext context = new StandardEvaluationContext(society); String expression = "members?.^[nationality == 'Serbian' || nationality == 'Idvor']"; [id="CO7-1"][id="CO1-11"][id="CO7-1"](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` 列表上使用“空安全选择第一个”操作符
- Kotlin
-
val parser = SpelExpressionParser() val society = IEEE() val context = StandardEvaluationContext(society) val expression = "members?.^[nationality == 'Serbian' || nationality == 'Idvor']" [id="CO8-1"][id="CO1-12"][id="CO8-1"](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
-
ExpressionParser parser = new SpelExpressionParser(); IEEE society = new IEEE(); StandardEvaluationContext context = new StandardEvaluationContext(society); String expression = "members?.$[nationality == 'Serbian' || nationality == 'Idvor']"; [id="CO9-1"][id="CO1-13"][id="CO9-1"](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` 列表上使用“空安全选择最后一个”操作符
- Kotlin
-
val parser = SpelExpressionParser() val society = IEEE() val context = StandardEvaluationContext(society) val expression = "members?.$[nationality == 'Serbian' || nationality == 'Idvor']" [id="CO10-1"][id="CO1-14"][id="CO10-1"](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
-
ExpressionParser parser = new SpelExpressionParser(); IEEE society = new IEEE(); StandardEvaluationContext context = new StandardEvaluationContext(society); // evaluates to ["Smiljan", "Idvor"] List placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") [id="CO11-1"][id="CO1-15"][id="CO11-1"](1) .getValue(context, List.class); society.members = null; // evaluates to null - does not throw a NullPointerException placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") [id="CO11-2"][id="CO1-16"][id="CO11-2"](2) .getValue(context, List.class);
<1> 在非空 `members` 列表上使用空安全投影操作符 <1> 在空 `members` 列表上使用空安全投影操作符
- Kotlin
-
val parser = SpelExpressionParser() val society = IEEE() val context = StandardEvaluationContext(society) // evaluates to ["Smiljan", "Idvor"] var placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") [id="CO12-1"][id="CO1-17"][id="CO12-1"](1) .getValue(context, List::class.java) society.members = null // evaluates to null - does not throw a NullPointerException placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") [id="CO12-2"][id="CO1-18"][id="CO12-2"](2) .getValue(context, List::class.java)
<1> 在非空 `members` 列表上使用空安全投影操作符 <1> 在空 `members` 列表上使用空安全投影操作符
Optional 上的空安全操作
自 Spring Framework 7.0 起,java.util.Optional 实例上支持空安全操作,具有透明的解包语义。
具体来说,当空安全操作符应用于一个_空的_ Optional 时,它将被视为 Optional 为 null,并且后续操作将求值为 null。但是,如果空安全操作符应用于一个非空的 Optional,则后续操作将应用于 Optional 中包含的对象,从而有效地解包 Optional。
例如,如果 user 的类型是 Optional<User>,则表达式 user?.name 在 user 为 null 或_空的_ Optional 时将求值为 null,否则将求值为 user 的 name,实际上是 user.get().getName() 或 user.get().name(分别用于属性或字段访问)。
|
在_空的_ |
类似地,如果 names 的类型是 Optional<List<String>>,则表达式
names?.?[#this.length > 5] 在 names 为 null 或_空的_ 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
-
ExpressionParser parser = new SpelExpressionParser(); IEEE society = new IEEE(); StandardEvaluationContext context = new StandardEvaluationContext(society); String expression = "members?.^[nationality == 'Serbian']?.name"; [id="CO13-1"][id="CO1-19"][id="CO13-1"](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> 在复合表达式中使用“空安全选择第一个”和空安全属性访问操作符。
- Kotlin
-
val parser = SpelExpressionParser() val society = IEEE() val context = StandardEvaluationContext(society) val expression = "members?.^[nationality == 'Serbian']?.name" [id="CO14-1"][id="CO1-20"][id="CO14-1"](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> 在复合表达式中使用“空安全选择第一个”和空安全属性访问操作符。