Skip to content

集合选择 (Collection Selection)

选择(Selection)是表达式语言的一项强大功能,它允许你通过从源集合的条目中进行筛选,将该集合转换为另一个集合。

选择使用的语法为 .?[selectionExpression]。它会对集合进行过滤,并返回一个包含原始元素子集的新集合。例如,通过选择功能,我们可以轻松获取塞尔维亚发明家列表:

java
// 获取国籍为 'Serbian' 的发明家列表
List<Inventor> list = (List<Inventor>) parser.parseExpression(
		"members.?[nationality == 'Serbian']").getValue(societyContext);
kotlin
// 获取国籍为 'Serbian' 的发明家列表
val list = parser.parseExpression(
		"members.?[nationality == 'Serbian']").getValue(societyContext) as List<Inventor>

选择功能支持数组以及任何实现了 java.lang.Iterablejava.util.Map 的对象。

  • 对于数组或 Iterable:选择表达式会针对每个单独的元素进行评估。
  • 对于Map:选择表达式会针对每个 Map 条目(Java 类型为 Map.Entry的对象)进行评估。每个 Map 条目的 keyvalue 都可以作为属性在选择表达式中使用。

假设变量 #map 中存储了一个 Map,以下表达式将返回一个新的 Map,其中包含原始 Map 中所有其值(value)小于 27 的元素:

java
Map newMap = parser.parseExpression("#map.?[value < 27]").getValue(Map.class);
kotlin
val newMap = parser.parseExpression("#map.?[value < 27]").getValue() as Map

除了返回所有选定的元素外,你还可以只获取第一个或最后一个元素。

  • 获取匹配表达式的第一项:语法为 .^[selectionExpression]
  • 获取匹配表达式的最后一项:语法为 .$[selectionExpression]

注意

Spring 表达式语言同样支持集合选择的安全导航。当源集合为 null 时,使用 ?.? 等运算符可以避免抛出空指针异常。


补充教学

1. SpEL 选择 vs Java Stream 过滤

你可以将 SpEL 的选择语法看作是 Java 8+ Stream API 的一种极简表达方式:

  • SpEL: list.?[age > 18]
  • Java: list.stream().filter(e -> e.getAge() > 18).collect(Collectors.toList()) 在 Spring Data JPA 的 @Query 或 Spring Security 的 @PostFilter 中,使用 SpEL 选择语法通常比手动写 Stream 转换要简洁得多。

2. 隐式的 #this 变量

在选择表达式内部,SpEL 实际上将当前正在处理的元素暴露为 #this 变量。

  • 如果元素是普通对象,你可以直接写属性名(如 age > 18),它相当于 #this.age > 18
  • 如果集合里存的是基本类型(如 List<Integer>),则必须显式使用 #this,例如:#numbers.?[#this > 10]

3. Map 选择的特殊性

在处理 Map 时,SpEL 自动解包了 Map.Entry

  • 你可以直接使用 key 表示键,value 表示值。
  • 筛选键#map.?[key.startsWith('admin')]
  • 筛选值#map.?[value.enabled == true] 这在动态生成的统计信息或配置映射中非常实用。

4. 性能考量:急切计算 (Eager Evaluation)

由于 SpEL 的选择操作会创建一个全新的集合实例来存储结果,因此:

  • 内存开销:如果源集合非常庞大,频繁的选择操作可能会产生较多的临时对象。
  • 修改语义:对返回的新集合进行增删操作不会影响原始集合。

5. 与安全引用的完美组合

在实际开发中,集合本身可能为 null,此时建议配合安全引用运算符:

  • 推荐写法user?.roles?.?[name == 'ADMIN']
  • 解析
    1. user?:防止用户对象为空。
    2. roles?:防止角色列表为空。
    3. ?.?:如果前两级都有值且列表非空,则执行筛选;否则整个表达式优雅返回 null

Based on Spring Framework.