安全導航運算子
安全導航運算子 (?.) 用於避免 NullPointerException,它源自 Groovy 語言。通常,當你引用一個物件時,可能需要在訪問該物件的方法或屬性之前驗證它是否為 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 屬性上使用安全導航運算子 |
|
安全導航運算子也適用於物件上的方法呼叫。 例如,如果上下文中未配置 |
安全索引訪問
自 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 應用空安全運算子時,它將被視為 Optional 為 null,並且後續操作將評估為 null。但是,如果對一個非空 Optional 應用空安全運算子,則後續操作將應用於 Optional 中包含的物件,從而有效地解包 Optional。
例如,如果 user 的型別為 Optional<User>,則表示式 user?.name 將在 user 為 null 或空 Optional 時評估為 null,否則將評估為 user 的 name,這實際上是屬性或欄位訪問的 user.get().getName() 或 user.get().name。
|
對空的 |
同樣,如果 names 的型別為 Optional<List<String>>,則表示式 names?.?[#this.length > 5] 將在 names 為 null 或空的 Optional 時評估為 null,否則將評估為包含長度大於 5 的名稱序列,這實際上是 names.get().stream().filter(s → s.length() > 5).toList()。
本章前面提到的所有空安全運算子都適用相同的語義。
有關更多詳細資訊和示例,請查閱以下運算子的 javadoc。
複合表示式中的空安全操作
如本節開頭所述,當複合表示式中的某個空安全操作的安全導航運算子評估為 null 時,複合表示式的其餘部分仍將繼續評估。這意味著必須在整個複合表示式中應用安全導航運算子,以避免任何不必要的 NullPointerException。
給定表示式 #person?.address.city,如果 #person 為 null,安全導航運算子 (?.) 可確保在嘗試訪問 #person 的 address 屬性時不會丟擲異常。但是,由於 #person?.address 評估為 null,在嘗試訪問 null 的 city 屬性時將丟擲 NullPointerException。為了解決這個問題,您可以在整個複合表示式中應用空安全導航,如 #person?.address?.city。如果 #person 或 #person?.address 評估為 null,該表示式將安全地評估為 null。
以下示例演示瞭如何在複合表示式中將“空安全選擇第一個”運算子 (?.^) 與空安全屬性訪問 (?.) 結合使用。如果 members 為 null,“空安全選擇第一個”運算子的結果 (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 | 在複合表示式中使用“空安全選擇第一個”和空安全屬性訪問運算子。 |