核心概念

  1. 比较运算符 (Comparison Operators):用于比较两个值的大小或相等性,返回布尔值 (truefalse)。
  2. 布尔值 (Boolean):逻辑值,true (真) 或 false (假),是条件判断的基础。
  3. 数据类型 (Data Types):FreeMarker 支持字符串、数字、日期/时间、布尔值、哈希表、序列等。比较运算符的行为依赖于操作数的类型。
  4. 相等性 (Equality)
    • 值相等 (==, =):比较两个值是否在语义上相等(内容相同)。
    • 同一性 (===):比较两个值是否是同一个对象实例(内存地址相同,主要用于宏、函数、方法引用)。
  5. 大小比较 (<, <=, >, >=):主要用于数字、日期/时间的排序比较。
  6. ? 操作符:FreeMarker 的内建函数(Built-ins)访问符,也用于处理 null 值。
  7. ?? 操作符:检查变量是否存在(不为 null)。
  8. ! 操作符:提供默认值(当左侧为 null 时使用右侧值)。

操作步骤 (非常详细)

步骤 1: 理解基本比较运算符

  • 相等 (===)
    • === 在 FreeMarker 中是等价的,都表示“值相等”。推荐使用 == 以提高可读性。
    • 比较两个值的内容是否相同。
    • 示例:
      <#assign a = 5>
      <#assign b = 5>
      <#if a == b>
        a 等于 b <!-- 此块将执行 -->
      </#if>
      
  • 不等 (!=)
    • 判断两个值是否不相等。
    • 示例:
      <#assign name = "Alice">
      <#if name != "Bob">
        名字不是 Bob <!-- 此块将执行 -->
      </#if>
      
  • 小于 (<)
    • 判断左侧值是否小于右侧值。
    • 主要用于数字和日期。
    • 示例:
      <#assign age = 18>
      <#if age < 21>
        未满 21 岁
      </#if>
      
  • 小于等于 (<=)
    • 判断左侧值是否小于或等于右侧值。
    • 示例:
      <#if score <= 100>
        分数有效
      </#if>
      
  • 大于 (>)
    • 判断左侧值是否大于右侧值。
    • 示例:
      <#if temperature > 30>
        天气炎热
      </#if>
      
  • 大于等于 (>=)
    • 判断左侧值是否大于或等于右侧值。
    • 示例:
      <#if quantity >= minimum>
        库存充足
      </#if>
      

步骤 2: 处理 null

  • ?? 操作符 (存在性检查)
    • 检查变量是否已定义且不为 null
    • 返回布尔值。
    • 示例:
      <#if user.email??>
        邮箱: ${user.email}
      <#else>
        邮箱未提供
      </#if>
      
  • ! 操作符 (提供默认值)
    • 如果左侧表达式为 null,则使用 ! 后面的值;否则使用左侧表达式的值。
    • 常用于避免 null 引起的错误。
    • 示例:
      <#assign displayName = user.nickname!user.username!"Anonymous">
      <!-- 如果 user.nickname 不为 null 则用它;否则用 user.username;如果都为 null 则用 "Anonymous" -->
      
  • 在比较中直接处理 null
    • 直接将变量与 null 比较。
    • 示例:
      <#if user.profilePicture == null>
        使用默认头像
      <#else>
        <img src="${user.profilePicture}" alt="Profile">
      </#if>
      

步骤 3: 使用 === (同一性比较)

  • 概念=== 比较两个引用是否指向同一个对象实例。
  • 应用场景:通常用于比较宏、函数、方法引用或自定义对象是否是同一个。
  • 示例
    <#function getHandler>...</#function>
    <#assign handler1 = getHandler()>
    <#assign handler2 = getHandler()>
    <#if handler1 === handler2>
      <!-- 通常为 false,因为 getHandler() 每次返回新实例 -->
    </#if>
    <#assign sameHandler = handler1>
    <#if handler1 === sameHandler>
      <!-- true,因为 sameHandler 指向 handler1 的同一个实例 -->
    </#if>
    
  • 注意:对于字符串、数字等基本类型,===== 通常行为一致(因为它们是不可变的且可能被缓存),但语义上 == 是值相等,=== 是同一性。推荐在比较基本类型时使用 ==

步骤 4: 在条件判断 (<#if>, <#elseif>, <#else>) 中使用比较

  • 这是比较运算符最常见的使用场景。
  • 示例:多条件判断
    <#assign score = 85>
    <#if score >= 90>
      优秀
    <#elseif score >= 80>
      良好 <!-- 此块将执行 -->
    <#elseif score >= 70>
      中等
    <#else>
      需要努力
    </#if>
    
  • 示例:结合 ??!
    <#if (user.age!) >= 18 && user.email??>
      成年用户且有邮箱
    </#if>
    

步骤 5: 在循环 (<#list>) 中使用比较

  • 常用于根据索引或元素属性进行条件处理。
  • 示例:处理列表的第一个和最后一个元素
    <#list items as item>
      <#if item_index == 0>
        <li class="first">${item}</li>
      <#elseif item_index == item_last>
        <li class="last">${item}</li>
      <#else>
        <li>${item}</li>
      </#if>
    </#list>
    
  • 示例:根据元素属性过滤或高亮
    <#list products as product>
      <#if product.status == "out_of_stock">
        <div class="out-of-stock">${product.name} (缺货)</div>
      <#elseif product.price > 1000>
        <div class="premium">${product.name} (高端)</div>
      <#else>
        <div>${product.name}</div>
      </#if>
    </#list>
    

步骤 6: 结合逻辑运算符 (&&, ||, !)

  • 使用逻辑运算符组合多个比较表达式。
  • && (逻辑与):两边都为 true 时结果为 true
  • || (逻辑或):至少一边为 true 时结果为 true
  • ! (逻辑非):取反。
  • 示例
    <#if (user.age >= 18) && (user.country == "CN" || user.country == "US")>
      符合条件的用户
    </#if>
    <#if !(user.isBlocked)>
      用户未被封禁
    </#if>
    

步骤 7: 使用内建函数辅助比较

  • ?number:将字符串转换为数字后再比较。
    <#assign strNum = "123">
    <#if strNum?number > 100>
      字符串转数字后大于 100
    </#if>
    
  • ?datetime, ?date, ?time:确保日期/时间格式正确。
    <#if eventDate?datetime >= now?datetime>
      活动尚未开始
    </#if>
    
  • ?lower_case, ?upper_case:进行不区分大小写的比较。
    <#if user.role?lower_case == "admin">
      管理员权限
    </#if>
    
  • ?index_of, ?contains:用于字符串或序列的包含性检查(可视为一种比较)。
    <#if userName?index_of("John") != -1>
      名字包含 John
    </#if>
    <#if tags?seq_contains("java")>
      标签包含 java
    </#if>
    

步骤 8: 在 Java 代码中传递数据并渲染

  • 确保 Java 层传递的数据类型与模板中的比较预期一致。
  • 示例 (Java):
    Map<String, Object> dataModel = new HashMap<>();
    dataModel.put("score", 95); // Integer
    dataModel.put("userName", "Alice"); // String
    dataModel.put("isPremium", true); // Boolean
    dataModel.put("user", userBean); // 包含 age, email 等属性的 Bean
    // 渲染模板...
    

常见错误

  1. 使用 = 而不是 ==:虽然 = 在 FreeMarker 中也表示相等,但 == 更标准且不易与赋值混淆(尽管 FreeMarker 赋值用 assign)。
  2. null 值导致异常
    • 尝试访问 null 对象的属性或方法:${user.profile.color}usernull 时会报错。
    • 解决:使用 ??! 操作符。
  3. 类型不匹配
    • 比较字符串和数字:"123" == 123 通常为 false,因为一个是字符串,一个是数字。
    • 解决:使用 ?number 将字符串转为数字,或确保数据类型一致。
  4. 混淆 =====:在需要值相等时错误地使用 ===,或反之。记住 == 是值相等,=== 是同一性。
  5. <#if> 中忘记括号:当条件复杂时,最好用括号明确优先级。
    <#if a == 1 && b == 2 || c == 3> <!-- 优先级可能不明确 -->
    <#if (a == 1 && b == 2) || c == 3> <!-- 更清晰 -->
    
  6. 字符串比较区分大小写"Admin" == "admin"false
    • 解决:使用 ?lower_case?upper_case
  7. 在循环中错误使用索引变量:误用 item_index (从 0 开始) 或 item_has_next 等。
  8. 逻辑运算符优先级错误&& 优先级高于 ||,必要时使用括号。

注意事项

  1. null 是第一公民:始终考虑变量可能为 null 的情况,并使用 ??! 进行防护。
  2. 类型安全:FreeMarker 是动态类型,但比较时类型很重要。尽量保证比较双方类型一致。
  3. 字符串比较
    • 默认区分大小写。
    • 使用 ?starts_with, ?ends_with, ?contains 进行子串检查。
    • 使用 ?index_of 获取位置。
  4. 数字比较
    • 整数和浮点数可以比较。
    • 注意浮点数精度问题(虽然 FreeMarker 通常能处理)。
  5. 日期/时间比较
    • 确保比较的日期/时间是相同类型(都是 datetime, date, 或 time)。
    • 使用 now 变量获取当前时间。
  6. 布尔值比较
    • 直接使用布尔变量:<#if isActive> 等同于 <#if isActive == true>
    • 取反:<#if !isActive>
  7. 作用域:在宏、函数或 #local/#global 中定义的变量有特定作用域,影响其可访问性和比较。
  8. 性能:简单的比较运算非常快。避免在循环内部进行昂贵的函数调用或复杂计算。

使用技巧

  1. 利用 ! 提供默认值简化比较
    <#if (user.preference.theme!"light") == "dark">
      使用深色主题
    </#if>
    
  2. 使用 ?default 内建函数:与 ! 类似,但语法不同。
    <#if user.age?default(0) >= 18>
      成年
    </#if>
    
  3. ?has_content 内建函数
    • 对于字符串:检查非 null 且长度 > 0。
    • 对于集合/序列:检查非 null 且大小 > 0。
    • 对于数字:总是 true (除了 null)。
    • 示例
      <#if user.comments?has_content>
        <h3>评论:</h3>
        <#list user.comments as comment>...</#list>
      </#if>
      
  4. 组合内建函数
    <#if user.email?lower_case?contains("@gmail.com")>
      Gmail 用户
    </#if>
    
  5. 使用宏封装复杂比较逻辑
    <#macro checkEligibility age country>
      <#return (age >= 18) && (country == "US" || country == "CA")>
    </#macro>
    <#if checkEligibility(user.age, user.country)>
      符合资格
    </#if>
    

最佳实践与性能优化

  1. 优先处理 null:在任何访问属性或进行比较前,先用 ??! 处理潜在的 null 值。
  2. 保持条件简单:复杂的条件表达式可拆分为多个 assign 变量或使用宏。
    <#assign isAdult = (user.age!0) >= 18>
    <#assign isInRegion = user.country?lower_case == "cn">
    <#if isAdult && isInRegion>
      ...
    </#if>
    
  3. 在 Java 层处理复杂业务逻辑:将复杂的计算、数据聚合、权限判断等放在 Java 代码中,传递布尔标志或预处理后的数据到模板。
  4. 重用 Configuration 和缓存 Template:这是 FreeMarker 性能的关键,与比较运算符本身无关,但影响整体渲染性能。
  5. 避免在循环中重复计算:如果比较条件依赖于不随循环变化的变量,可将其计算移出循环。
  6. 使用合适的内建函数?has_content 比手动检查 ??.size > 0 更简洁高效。
  7. 代码格式化与可读性:使用换行和缩进使复杂的 <#if> 结构清晰易读。
  8. 版本兼容性:注意 FreeMarker 版本升级可能带来的行为变化(如 = vs == 的推荐用法)。
  9. 测试边界情况:特别是 null、空字符串、零值、最大/最小值等。
  10. 利用 IDE 插件:使用支持 FreeMarker 语法高亮和检查的 IDE 插件,减少语法错误。

通过掌握这些核心概念、详细步骤、规避常见错误、注意关键事项、运用技巧并遵循最佳实践,您将能够熟练、高效且安全地在 FreeMarker 模板中使用比较运算符。