在 FreeMarker 模板引擎中,集合操作是非常常见的功能,尤其在处理 Java 中的 List
、Map
、数组等结构时。本文将系统性地介绍 FreeMarker 中的集合操作函数:size
, first
, last
, reverse
, sort
, sort_by
,并涵盖核心概念、操作步骤、常见错误、注意事项、使用技巧、最佳实践与性能优化等内容,帮助开发者快速掌握并高效实践。
一、核心概念
操作 | 说明 |
---|---|
size |
获取集合的元素个数 |
first |
获取集合的第一个元素 |
last |
获取集合的最后一个元素 |
reverse |
反转集合顺序 |
sort |
对集合进行排序(按自然顺序) |
sort_by |
按照指定属性排序集合(适用于对象集合) |
⚠️ 注意: 这些内建函数仅适用于可迭代对象,如
List
、数组、Set
(部分支持)等。
二、操作步骤(详细)
步骤 1:准备数据模型
确保传递给模板的数据中包含集合类型(如 List
、Map
等)。
List<String> fruits = Arrays.asList("apple", "banana", "orange");
List<Map<String, Object>> users = new ArrayList<>();
users.add(Map.of("name", "Alice", "age", 25));
users.add(Map.of("name", "Bob", "age", 22));
users.add(Map.of("name", "Charlie", "age", 30));
Map<String, Object> model = new HashMap<>();
model.put("fruits", fruits);
model.put("users", users);
步骤 2:在模板中使用集合操作
2.1 获取集合大小 ?size
集合大小:${fruits?size}
输出:
集合大小:3
2.2 获取第一个元素 ?first
第一个元素:${fruits?first}
输出:
第一个元素:apple
2.3 获取最后一个元素 ?last
最后一个元素:${fruits?last}
输出:
最后一个元素:orange
2.4 反转集合 ?reverse
反转后的集合:
<#list fruits?reverse as fruit>
${fruit}
</#list>
输出:
反转后的集合:
orange
banana
apple
2.5 自然排序 ?sort
自然排序后的集合:
<#list fruits?sort as fruit>
${fruit}
</#list>
输出(按字母顺序):
apple
banana
orange
2.6 按属性排序 ?sort_by
按年龄排序的用户:
<#list users?sort_by("age") as user>
${user.name} - ${user.age}
</#list>
输出:
Bob - 22
Alice - 25
Charlie - 30
三、格式与语法说明
3.1 ?sort
的排序规则
- 对字符串按字母顺序排序(区分大小写)
- 对数字按数值大小排序
- 对布尔值:
false < true
- 对
Map
或 JavaBean,需要指定?sort_by
字段
3.2 ?sort_by
的嵌套属性
支持嵌套属性访问:
<#list users?sort_by("address.city") as user>
四、常见错误与注意事项
4.1 类型错误:非集合类型使用集合操作
错误示例:
${"hello"?size}
错误原因: 字符串不是集合类型。
解决办法: 确保操作对象是 List
、Map
、数组等。
4.2 ?sort_by
字段不存在或类型不一致
错误示例:
<#list users?sort_by("gender") as user>
错误原因: gender
属性在某些对象中不存在。
解决办法:
- 确保所有对象都有该字段
- 或使用
!
提供默认值:?sort_by("gender!unknown")
4.3 ?reverse
对 Map
无效
Map
不是有序结构,使用 reverse
可能无效果或抛异常(取决于版本)。
五、使用技巧
5.1 使用 <#assign>
缓存处理后的集合
<#assign sortedUsers = users?sort_by("age")>
避免重复计算,提高性能。
5.2 多条件排序(模拟)
FreeMarker 本身不支持多字段排序,但可以借助 Java 工具类或模板逻辑模拟:
model.put("multiSort", (Comparator<Map<String, Object>>) (o1, o2) -> {
int cmp = Integer.compare((Integer) o1.get("age"), (Integer) o2.get("age"));
if (cmp == 0) {
cmp = ((String) o1.get("name")).compareTo((String) o2.get("name"));
}
return cmp;
});
模板中:
<#assign sortedUsers = users?sort(multiSort)>
5.3 使用 ?chunk
分组显示集合
<#list fruits?chunk(2) as group>
<#list group as fruit>${fruit} </#list><br>
</#list>
输出:
apple banana
orange
六、最佳实践
实践 | 说明 |
---|---|
✅ 尽量在 Java 层处理复杂排序 | 提升模板可读性和性能 |
✅ 使用 ?assign 缓存中间结果 |
避免重复计算 |
✅ 对 Map 排序前转换为 List |
保证排序逻辑清晰 |
✅ 使用 ?chunk 实现分页展示 |
如表格分组、轮播图等场景 |
✅ 为 sort_by 提供默认值 |
如 ?sort_by("age!0") 避免空值报错 |
七、性能优化建议
优化点 | 说明 |
---|---|
❌ 避免在 <#list> 中重复调用 ?sort / ?reverse |
重复排序影响性能 |
✅ 提前在 Java 层排序 | 适合大数据量 |
✅ 使用 <#assign> 缓存结果 |
避免重复计算 |
✅ 对大集合进行分页处理 | 避免一次性渲染过多数据 |
八、总结
操作 | 用途 | 适用对象 |
---|---|---|
?size |
获取集合长度 | List、Map、Array |
?first |
获取第一个元素 | List、Array |
?last |
获取最后一个元素 | List、Array |
?reverse |
反转集合顺序 | List、Array |
?sort |
自然排序 | List(支持 Comparable) |
?sort_by |
按字段排序 | List |
九、附录:常用表达式示例汇总
表达式 | 输出示例 |
---|---|
${fruits?size} |
3 |
${fruits?first} |
apple |
${fruits?last} |
orange |
${fruits?reverse} |
orange, banana, apple |
${fruits?sort} |
apple, banana, orange |
${users?sort_by("age")} |
按年龄排序 |
${users?sort_by("name")} |
按名字排序 |
${users?sort_by("age")?first.name} |
获取最小年龄用户名称 |
十、扩展建议
如需更复杂集合操作(如分组、过滤、合并等),推荐:
- 在 Java 层使用
Stream API
或Guava
等工具类处理后再传入模板 - 在模板中使用宏(
<#macro>
)封装常用逻辑,提高复用性
如需进一步封装,可以创建一个 CollectionTool
类,提供 filter
, map
, groupBy
, limit
等功能,供模板调用。
✅ 结语: FreeMarker 的集合操作虽简单,但掌握其细节和最佳实践,可以极大提升模板开发效率和性能表现。结合 Java 层处理和模板逻辑,能构建出更强大、灵活的视图层逻辑。