安全导航操作符

安全导航操作符 (?.) 用于避免 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` 属性上使用安全导航操作符

安全导航操作符也适用于对象上的方法调用。 例如,如果 #calculator 变量未在上下文中配置,则表达式 #calculator?.max(4, 2) 求值为 null。 否则,将在 #calculator 上调用 max(int, int) 方法。

安全索引访问

自 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` 列表上使用空安全索引操作符

安全集合选择和投影

Spring 表达式语言通过以下操作符支持 集合选择集合投影 的安全导航。

  • 空安全选择: ?.?

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

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

  • 空安全投影: ?.!

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

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>  在可能为空的 `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>  在可能为空的 `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>  在可能为空的 `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>  在可能为空的 `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>  在可能为空的 `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>  在可能为空的 `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` 列表上使用空安全投影操作符

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

如本节开头所述,当安全导航操作符在复合表达式中对特定的空安全操作求值为 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
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>  在复合表达式中使用“空安全选择第一个”和空安全属性访问操作符。