安全导航操作符

安全导航操作符(?)用于避免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"));

// 评估为 "Smiljan"
String city = parser.parseExpression("placeOfBirth?.city") (1)
		.getValue(context, tesla, String.class);

tesla.setPlaceOfBirth(null);

// 评估为 null - 不会抛出 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"))

// 评估为 "Smiljan"
var city = parser.parseExpression("placeOfBirth?.city") (1)
		.getValue(context, tesla, String::class.java)

tesla.setPlaceOfBirth(null)

// 评估为 null - 不会抛出 NullPointerException
city = parser.parseExpression("placeOfBirth?.city") (2)
		.getValue(context, tesla, String::class.java)
1 在非空placeOfBirth属性上使用安全导航操作符
2 在空placeOfBirth属性上使用安全导航操作符

安全导航操作符也适用于对象的方法调用。

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

安全的集合选择和投影

Spring表达式语言支持通过以下运算符对集合进行安全导航的集合选择集合投影

  • 空安全选择: ?.?

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

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

  • 空安全投影: ?.!

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

  • Java

  • Kotlin

ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression = "members?.?[nationality == 'Serbian']"; (1)

// 评估为 [Inventor("Nikola Tesla")]
List<Inventor> list = (List<Inventor>) parser.parseExpression(expression)
		.getValue(context);

society.members = null;

// 评估为 null - 不会抛出空指针异常
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)

// 评估为 [Inventor("Nikola Tesla")]
var list = parser.parseExpression(expression)
		.getValue(context) as List<Inventor>

society.members = null

// 评估为 null - 不会抛出空指针异常
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)

// 评估为 Inventor("Nikola Tesla")
Inventor inventor = parser.parseExpression(expression)
		.getValue(context, Inventor.class);

society.members = null;

// 评估为 null - 不会抛出空指针异常
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)

// 评估为 Inventor("Nikola Tesla")
var inventor = parser.parseExpression(expression)
		.getValue(context, Inventor::class.java)

society.members = null

// 评估为 null - 不会抛出空指针异常
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)

// 评估为 Inventor("Pupin")
Inventor inventor = parser.parseExpression(expression)
		.getValue(context, Inventor.class);

society.members = null;

// 评估为 null - 不会抛出空指针异常
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)

// 评估为 Inventor("Pupin")
var inventor = parser.parseExpression(expression)
		.getValue(context, Inventor::class.java)

society.members = null

// 评估为 null - 不会抛出空指针异常
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);

// 评估为 ["Smiljan", "Idvor"]
List placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") (1)
		.getValue(context, List.class);

society.members = null;

// 评估为 null - 不会抛出空指针异常
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") (2)
		.getValue(context, List.class);
1 在非null的members列表上使用空安全投影运算符
2 在null的members列表上使用空安全投影运算符
val parser = SpelExpressionParser()
val society = IEEE()
val context = StandardEvaluationContext(society)

// 评估为 ["Smiljan", "Idvor"]
var placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") (1)
		.getValue(context, List::class.java)

society.members = null

// 评估为 null - 不会抛出空指针异常
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") (2)
		.getValue(context, List::class.java)
1 在非null的members列表上使用空安全投影运算符
2 在null的members列表上使用空安全投影运算符

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

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

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

// 评估为 "Nikola Tesla"
String name = parser.parseExpression(expression)
		.getValue(context, String.class);

society.members = null;

// 评估为 null - 不会抛出 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)

// 评估为 "Nikola Tesla"
String name = parser.parseExpression(expression)
		.getValue(context, String::class.java)

society.members = null

// 评估为 null - 不会抛出 NullPointerException
name = parser.parseExpression(expression)
		.getValue(context, String::class.java)
1 在复合表达式中使用“空安全选择第一个”和空安全属性访问运算符。