集合选择 (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.Iterable 或 java.util.Map 的对象。
- 对于数组或
Iterable:选择表达式会针对每个单独的元素进行评估。 - 对于Map:选择表达式会针对每个 Map 条目(Java 类型为
Map.Entry的对象)进行评估。每个 Map 条目的key和value都可以作为属性在选择表达式中使用。
假设变量 #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'] - 解析:
user?:防止用户对象为空。roles?:防止角色列表为空。?.?:如果前两级都有值且列表非空,则执行筛选;否则整个表达式优雅返回null。