安全導航運算子

安全導航運算子 (?.) 用於避免 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 變數尚未在 context 中組態,則運算式 #calculator?.max(4, 2) 的評估結果為 null。 否則,將在 #calculator 上調用 max(int, int) 方法。

安全索引存取

自 Spring Framework 6.2 起,Spring 運算式語言支援針對以下結構類型進行安全導航索引:

以下範例示範如何使用安全導航運算子來索引列表 (?.[])。

  • 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 運算式語言支援透過以下運算子進行 集合選擇集合投影

  • 空值安全選擇:?.?

  • 空值安全選擇第一個:?.^

  • 空值安全選擇最後一個:?.$

  • 空值安全投影:?.!

以下範例示範如何使用安全導航運算子進行集合選擇 (?.?)。

  • 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 在可能為空值的 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 在可能為空值的 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 在可能為空值的 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 在可能為空值的 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 在可能為空值的 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 在可能為空值的 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 在複合運算式中使用「空值安全選擇第一個」和空值安全屬性存取運算子。