安全导航运算符

安全导航运算符 (?.) 用于避免 NullPointerException,它源自 Groovy 语言。通常,当你引用一个对象时,可能需要在访问该对象的方法或属性之前验证它是否为 null。为了避免这种情况,安全导航运算符会针对特定的空安全操作返回 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 属性上使用安全导航运算符

安全导航运算符也适用于对象上的方法调用。

例如,如果上下文中未配置 #calculator 变量,则表达式 #calculator?.max(4, 2) 将评估为 null。否则,将对 #calculator 调用 max(int, int) 方法。

安全索引访问

自 Spring Framework 6.2 起,Spring Expression Language 支持对以下类型的结构进行索引时的安全导航。

以下示例演示了如何将安全导航运算符用于列表索引 (?.[])。

  • 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 列表上使用空安全索引运算符

安全集合选择和投影

Spring Expression Language 通过以下运算符支持集合选择和集合投影的安全导航。

  • 空安全选择: ?.?

  • 空安全选择第一个: ?.^

  • 空安全选择最后一个: ?.$

  • 空安全投影: ?.!

以下示例演示了如何将安全导航运算符用于集合选择 (?.?)。

  • 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 起,支持对 java.util.Optional 实例执行空安全操作,并具有透明的解包语义。

具体来说,当对一个空的 Optional 应用空安全运算符时,它将被视为 Optionalnull,并且后续操作将评估为 null。但是,如果对一个非空 Optional 应用空安全运算符,则后续操作将应用于 Optional 中包含的对象,从而有效地解包 Optional

例如,如果 user 的类型为 Optional<User>,则表达式 user?.name 将在 usernull Optional 时评估为 null,否则将评估为 username,这实际上是属性或字段访问的 user.get().getName()user.get().name

空的 Optional 仍然支持调用 Optional API 中定义的方法。例如,如果 name 的类型为 Optional<String>,则表达式 name?.orElse('Unknown') 将在 name 为空 Optional 时评估为 "Unknown",否则将评估为 name 为非空 OptionalOptional 中包含的 String,这实际上是 name.get()

同样,如果 names 的类型为 Optional<List<String>>,则表达式 names?.?⁠[#this.length > 5] 将在 namesnull空的 Optional 时评估为 null,否则将评估为包含长度大于 5 的名称序列,这实际上是 names.get().stream().filter(s → s.length() > 5).toList()

本章前面提到的所有空安全运算符都适用相同的语义。

有关更多详细信息和示例,请查阅以下运算符的 javadoc。

复合表达式中的空安全操作

如本节开头所述,当复合表达式中的某个空安全操作的安全导航运算符评估为 null 时,复合表达式的其余部分仍将继续评估。这意味着必须在整个复合表达式中应用安全导航运算符,以避免任何不必要的 NullPointerException

给定表达式 #person?.address.city,如果 #personnull,安全导航运算符 (?.) 可确保在尝试访问 #personaddress 属性时不会抛出异常。但是,由于 #person?.address 评估为 null,在尝试访问 nullcity 属性时将抛出 NullPointerException。为了解决这个问题,您可以在整个复合表达式中应用空安全导航,如 #person?.address?.city。如果 #person#person?.address 评估为 null,该表达式将安全地评估为 null

以下示例演示了如何在复合表达式中将“空安全选择第一个”运算符 (?.^) 与空安全属性访问 (?.) 结合使用。如果 membersnull,“空安全选择第一个”运算符的结果 (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 在复合表达式中使用“空安全选择第一个”和空安全属性访问运算符。
© . This site is unofficial and not affiliated with VMware.