安全導航運算子

安全導航運算子 (?.) 用於避免 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.