安全導航運算子

安全導航運算子(?.)用於避免 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?.max(4, 2) 如果上下文中的 #calculator 變數未配置,則評估為 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 列表上使用空安全投影運算子

複合表示式中的空安全操作

如本節開頭所述,當複合表示式中的特定空安全操作使用安全導航運算子評估為 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 在複合表示式中使用“空安全選擇第一個”和空安全屬性訪問運算子。