跳到主要内容
版本:7.0.3

安全导航运算符

Hunyuan 7b 中英对照 Safe Navigation Operator

安全导航运算符(?.)用于避免 NullPointerException,它来源于 Groovy 语言。通常,当你拥有一个对象的引用时,在访问该对象的方法或属性之前,你可能需要验证它是否为 null。为了避免这种情况,安全导航运算符会在特定操作因对象为 null 而无法执行时返回 null,而不会抛出异常。

注意

当安全导航操作符(safe navigation operator)在复合表达式中的某个空值安全操作(null-safe operation)中计算结果为 null 时,复合表达式的其余部分仍会继续被计算。

详情请参见复合表达式中的空值安全操作

安全的属性和方法访问

以下示例展示了如何使用安全导航运算符(?.)来访问属性。

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);
  • 对于非空的 placeOfBirth 属性,使用安全导航操作符

  • 对于为空的 placeOfBirth 属性,使用安全导航操作符

备注

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

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

安全的索引访问

从 Spring Framework 6.2 开始,Spring 表达式语言支持对以下类型的结构进行安全导航(即不会导致异常的索引访问)。

以下示例展示了如何使用安全导航运算符(?.[])来对列表进行索引操作。

ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
EvaluationContext context = new StandardEvaluationContext(society);

// 评估结果为 Inventor("Nikola Tesla")
Inventor inventor = parser.parseExpression("members?.[0]") 1
.getValue(context, Inventor.class);

society_members = null;

// 评估结果为 null - 不会抛出异常
inventor = parser.parseExpression("members?.[0}") 2
.getValue(context, Inventor.class);
  • 在非空 members 列表上使用空安全索引运算符

  • 在空 members 列表上使用空安全索引运算符

安全的集合选择与投影

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

  • 空值安全选择:?.?

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

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

  • 空值安全投影:?.!

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

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 - 不会抛出 NullPointerException
list = (List<Inventor>) parser.parseExpression(expression)
.getValue(context);
  • 在可能为 null 的 members 列表上使用空值安全选择运算符

以下示例展示了如何对集合使用“空值安全的首选选择”操作符(?.^)。

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 - 不会抛出 NullPointerException
inventor = parser.parseExpression(expression)
.getValue(context, Inventor.class);
  • 在可能为 null 的 members 列表上使用“null-safe select first”操作符

以下示例展示了如何对集合使用“空值安全的选择最后一个元素”操作符(?.$)。

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 - 不会抛出 NullPointerException
inventor = parser.parseExpression(expression)
.getValue(context, Inventor.class);
  • 在可能为 null 的 members 列表上使用“null-safe select last”操作符

以下示例展示了如何使用安全导航运算符(?.!)进行集合投影。

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 - 不会抛出 NullPointerException
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") 2
.getValue(context, List.class);
  • 在非空的 members 列表上使用空值安全投影运算符

  • 在空的 members 列表上使用空值安全投影运算符

Optional 的空值安全操作

从Spring Framework 7.0开始,java.util.Optional的实例支持空值安全操作(null-safe operations),并且具有透明的解包语义(transparent unwrapping semantics)。

具体来说,当一个空值安全(null-safe)运算符被应用于一个空的Optional对象时,它将被视为该Optional对象的值为null,因此后续的操作结果也将是null。然而,如果一个空值安全运算符被应用于一个非空的Optional对象,那么后续的操作将会作用于Optional对象所包含的实体上,从而有效地“解包”(unwrap)这个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是一个非空Optional时则计算为Optional中包含的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文档。

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

如本节开头所提到的,当安全导航运算符(safe navigation operator)在复合表达式中的某个特定“空值安全”操作(null-safe operation)中求值为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,而不会抛出异常。

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);
  • 在复合表达式中使用“null-safe select first”和null-safe属性访问操作符。