FreeMarker 是一个功能强大的 Java 模板引擎,广泛应用于动态内容生成、Web 页面渲染、代码生成等场景。在模板中,循环处理 是非常常见的需求,<#list> 是 FreeMarker 提供的核心循环指令,而 <#break> 则用于在循环中提前终止。本文将从 核心概念操作步骤常见错误注意事项使用技巧最佳实践性能优化 等多个维度,全面讲解 FreeMarker 中 <#list><#break> 的使用,帮助你快速掌握关键知识并实践。


一、核心概念

1. <#list> 指令

  • 作用:用于遍历集合(如 ListMap、数组等)。
  • 语法
    <#list collection as item>
      ...
    </#list>
    
  • 支持的集合类型
    • List
    • Map(遍历键)
    • 数组
    • Set
    • 可迭代对象(如 Iterator

2. <#break> 指令

  • 作用:在 <#list> 循环中提前终止循环。
  • 语法
    <#if condition><#break></#if>
    
  • 注意<#break> 只能在 <#list> 循环内部使用。

二、操作步骤(详细)

1. 遍历 List

<#list fruits as fruit>
  <p>${fruit}</p>
</#list>

Java 示例数据:

List<String> fruits = Arrays.asList("Apple", "Banana", "Orange");
model.put("fruits", fruits);

2. 遍历 Map(键值对)

<#list user?keys as key>
  <p>${key}: ${user[key]}</p>
</#list>

Java 示例数据:

Map<String, String> user = new HashMap<>();
user.put("name", "Alice");
user.put("email", "alice@example.com");
model.put("user", user);

3. 使用 <#break> 提前终止循环

<#list fruits as fruit>
  <#if fruit == "Banana">
    <#break>
  </#if>
  <p>${fruit}</p>
</#list>

说明:当 fruit 等于 "Banana" 时,循环终止,不再输出后续元素。


4. 嵌套循环

<#list users as user>
  <h3>User: ${user.name}</h3>
  <ul>
    <#list user.roles as role>
      <li>${role}</li>
    </#list>
  </ul>
</#list>

Java 示例数据:

class User {
    private String name;
    private List<String> roles;
    // getter/setter
}
List<User> users = Arrays.asList(
    new User("Alice", Arrays.asList("Admin", "User")),
    new User("Bob", Arrays.asList("User"))
);
model.put("users", users);

5. 使用 <#items><#no_item> 分支

<#list fruits as fruit>
  <#items>
    <p>${fruit}</p>
  </#items>
  <#no_item>
    <p>No fruits available.</p>
  </#no_item>
</#list>

说明:当 fruits 为空或 null 时,执行 <#no_item> 分支。


三、常见错误与注意事项

1. <#break> 被误用在非 <#list> 循环中

<#if condition><#break></#if>  <#-- 错误:不在循环中 -->

解决方法:确保 <#break> 仅在 <#list> 循环体内使用。


2. <#list> 传入 null 或 missing 值导致异常

<#list fruits as fruit>  <#-- 如果 fruits 为 null,抛出异常 -->

解决方法:使用 ! 提供默认空集合。

<#list fruits![] as fruit>

3. <#items><#no_item> 混用不当

<#list fruits as fruit>
  <#items>
    ...
  </#items>
</#list>

⚠️ 问题:缺少 <#no_item> 时,如果集合为空不会有任何输出。

建议:根据需求决定是否添加 <#no_item> 分支。


4. 循环变量名重复导致逻辑错误

<#list users as user>
  <#list user.roles as user>  <#-- 错误:变量名重复 -->

解决方法:避免变量名重复,如改为 role


四、使用技巧

1. 使用 item_index 获取当前索引(从 0 开始)

<#list fruits as fruit>
  <p>No. ${fruit_item_index + 1}: ${fruit}</p>
</#list>

自动生成的变量名:变量名 + _item_index


2. 使用 item_has_next 判断是否为最后一个元素

<#list fruits as fruit>
  ${fruit}<#if fruit_has_next>, </#if>
</#list>

3. 使用 <#break> 实现“取前 N 个”逻辑

<#assign count = 0>
<#list fruits as fruit>
  <#if count >= 2><#break></#if>
  <p>${fruit}</p>
  <#assign count = count + 1>
</#list>

4. 结合 ?size 获取集合长度

Total fruits: ${fruits?size}

五、最佳实践

1. 始终使用 ![] 防止 null 异常

<#list fruits![] as fruit>

2. 避免深层嵌套循环

  • 嵌套过深会导致模板可读性差。
  • 建议在 Java 层预处理数据。

3. 使用 <#items><#no_item> 提高可读性

  • 明确区分有数据和无数据的逻辑分支。

4. 使用 <#break> 控制循环逻辑(如取前 N 个、条件退出)

  • 但避免滥用,保持逻辑清晰。

5. 使用 item_indexitem_has_next 提升展示效果

  • 用于生成序号、分隔符等。

六、性能优化

1. 避免在循环中频繁调用方法

  • 方法调用会降低性能,建议在 Java 层预处理后传入模板。

2. 使用 ?size 替代手动计数

  • ?size 是内置函数,性能优于 <#assign> 计数。

3. 启用模板缓存

Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
cfg.setTemplateCacheSize(100);  // 设置缓存大小
cfg.setTemplateUpdateDelay(3600);  // 每小时检查一次更新

七、总结

技术点 说明 推荐用法
<#list> 遍历集合 <#list collection as item>
<#break> 提前终止循环 <#if condition><#break></#if>
item_index 获取索引 ${fruit_item_index}
item_has_next 判断是否为最后一个 <#if fruit_has_next>,</#if>
<#items><#no_item> 控制有无数据逻辑 <#items>...</#items><#no_item>...</#no_item>
![] 防止 null 异常 <#list fruits![] as fruit>

八、参考示例代码(Java + FTL)

Java 示例数据

Map<String, Object> model = new HashMap<>();

// List 示例
List<String> fruits = Arrays.asList("Apple", "Banana", "Orange");
model.put("fruits", fruits);

// Map 示例
Map<String, String> user = new HashMap<>();
user.put("name", "Alice");
user.put("email", "alice@example.com");
model.put("user", user);

// 嵌套结构示例
class User {
    private String name;
    private List<String> roles;
    public User(String name, List<String> roles) {
        this.name = name;
        this.roles = roles;
    }
    public String getName() { return name; }
    public List<String> getRoles() { return roles; }
}
List<User> users = Arrays.asList(
    new User("Alice", Arrays.asList("Admin", "User")),
    new User("Bob", Arrays.asList("User"))
);
model.put("users", users);

FTL 模板示例

<h2>List 循环</h2>
<#list fruits![] as fruit>
  <#if fruit == "Banana"><#break></#if>
  <p>${fruit}</p>
</#list>

<h2>Map 循环</h2>
<#list user?keys as key>
  <p>${key}: ${user[key]}</p>
</#list>

<h2>嵌套循环</h2>
<#list users as user>
  <h3>${user.name}</h3>
  <ul>
    <#list user.roles as role>
      <li>${role}</li>
    </#list>
  </ul>
</#list>

<h2>空集合处理</h2>
<#list emptyList![] as item>
  <#items>
    <p>${item}</p>
  </#items>
  <#no_item>
    <p>Empty list.</p>
  </#no_item>
</#list>

九、结语

熟练掌握 FreeMarker 中 <#list><#break> 的使用,是构建高效、可维护模板系统的关键。通过合理使用循环指令、索引控制、空值处理等技巧,可以显著提升模板渲染的灵活性和健壮性。在实际开发中,建议:

  • 所有集合访问都使用 ![] 防止 null 异常;
  • 使用 <#break> 控制循环流程;
  • 在 Java 层预处理复杂逻辑,减少模板负担;
  • 使用 <#items><#no_item> 提高可读性;
  • 启用模板缓存提升性能。

最终建议

  • 避免在模板中执行复杂逻辑;
  • 使用 <#break> 控制循环逻辑;
  • 始终使用 ![] 防止 null
  • 合理使用索引和判断逻辑增强展示效果。