核心概念
- 比较运算符 (Comparison Operators):用于比较两个值的大小或相等性,返回布尔值 (
true
或false
)。 - 布尔值 (Boolean):逻辑值,
true
(真) 或false
(假),是条件判断的基础。 - 数据类型 (Data Types):FreeMarker 支持字符串、数字、日期/时间、布尔值、哈希表、序列等。比较运算符的行为依赖于操作数的类型。
- 相等性 (Equality):
- 值相等 (
==
,=
):比较两个值是否在语义上相等(内容相同)。 - 同一性 (
===
):比较两个值是否是同一个对象实例(内存地址相同,主要用于宏、函数、方法引用)。
- 值相等 (
- 大小比较 (
<
,<=
,>
,>=
):主要用于数字、日期/时间的排序比较。 ?
操作符:FreeMarker 的内建函数(Built-ins)访问符,也用于处理null
值。??
操作符:检查变量是否存在(不为null
)。!
操作符:提供默认值(当左侧为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 // 渲染模板...
常见错误
- 使用
=
而不是==
:虽然=
在 FreeMarker 中也表示相等,但==
更标准且不易与赋值混淆(尽管 FreeMarker 赋值用assign
)。 null
值导致异常:- 尝试访问
null
对象的属性或方法:${user.profile.color}
当user
为null
时会报错。 - 解决:使用
??
或!
操作符。
- 尝试访问
- 类型不匹配:
- 比较字符串和数字:
"123" == 123
通常为false
,因为一个是字符串,一个是数字。 - 解决:使用
?number
将字符串转为数字,或确保数据类型一致。
- 比较字符串和数字:
- 混淆
==
和===
:在需要值相等时错误地使用===
,或反之。记住==
是值相等,===
是同一性。 - 在
<#if>
中忘记括号:当条件复杂时,最好用括号明确优先级。<#if a == 1 && b == 2 || c == 3> <!-- 优先级可能不明确 --> <#if (a == 1 && b == 2) || c == 3> <!-- 更清晰 -->
- 字符串比较区分大小写:
"Admin" == "admin"
为false
。- 解决:使用
?lower_case
或?upper_case
。
- 解决:使用
- 在循环中错误使用索引变量:误用
item_index
(从 0 开始) 或item_has_next
等。 - 逻辑运算符优先级错误:
&&
优先级高于||
,必要时使用括号。
注意事项
null
是第一公民:始终考虑变量可能为null
的情况,并使用??
或!
进行防护。- 类型安全:FreeMarker 是动态类型,但比较时类型很重要。尽量保证比较双方类型一致。
- 字符串比较:
- 默认区分大小写。
- 使用
?starts_with
,?ends_with
,?contains
进行子串检查。 - 使用
?index_of
获取位置。
- 数字比较:
- 整数和浮点数可以比较。
- 注意浮点数精度问题(虽然 FreeMarker 通常能处理)。
- 日期/时间比较:
- 确保比较的日期/时间是相同类型(都是
datetime
,date
, 或time
)。 - 使用
now
变量获取当前时间。
- 确保比较的日期/时间是相同类型(都是
- 布尔值比较:
- 直接使用布尔变量:
<#if isActive>
等同于<#if isActive == true>
。 - 取反:
<#if !isActive>
。
- 直接使用布尔变量:
- 作用域:在宏、函数或
#local
/#global
中定义的变量有特定作用域,影响其可访问性和比较。 - 性能:简单的比较运算非常快。避免在循环内部进行昂贵的函数调用或复杂计算。
使用技巧
- 利用
!
提供默认值简化比较:<#if (user.preference.theme!"light") == "dark"> 使用深色主题 </#if>
- 使用
?default
内建函数:与!
类似,但语法不同。<#if user.age?default(0) >= 18> 成年 </#if>
?has_content
内建函数:- 对于字符串:检查非
null
且长度 > 0。 - 对于集合/序列:检查非
null
且大小 > 0。 - 对于数字:总是
true
(除了null
)。 - 示例:
<#if user.comments?has_content> <h3>评论:</h3> <#list user.comments as comment>...</#list> </#if>
- 对于字符串:检查非
- 组合内建函数:
<#if user.email?lower_case?contains("@gmail.com")> Gmail 用户 </#if>
- 使用宏封装复杂比较逻辑:
<#macro checkEligibility age country> <#return (age >= 18) && (country == "US" || country == "CA")> </#macro> <#if checkEligibility(user.age, user.country)> 符合资格 </#if>
最佳实践与性能优化
- 优先处理
null
:在任何访问属性或进行比较前,先用??
或!
处理潜在的null
值。 - 保持条件简单:复杂的条件表达式可拆分为多个
assign
变量或使用宏。<#assign isAdult = (user.age!0) >= 18> <#assign isInRegion = user.country?lower_case == "cn"> <#if isAdult && isInRegion> ... </#if>
- 在 Java 层处理复杂业务逻辑:将复杂的计算、数据聚合、权限判断等放在 Java 代码中,传递布尔标志或预处理后的数据到模板。
- 重用
Configuration
和缓存Template
:这是 FreeMarker 性能的关键,与比较运算符本身无关,但影响整体渲染性能。 - 避免在循环中重复计算:如果比较条件依赖于不随循环变化的变量,可将其计算移出循环。
- 使用合适的内建函数:
?has_content
比手动检查??
和.size > 0
更简洁高效。 - 代码格式化与可读性:使用换行和缩进使复杂的
<#if>
结构清晰易读。 - 版本兼容性:注意 FreeMarker 版本升级可能带来的行为变化(如
=
vs==
的推荐用法)。 - 测试边界情况:特别是
null
、空字符串、零值、最大/最小值等。 - 利用 IDE 插件:使用支持 FreeMarker 语法高亮和检查的 IDE 插件,减少语法错误。
通过掌握这些核心概念、详细步骤、规避常见错误、注意关键事项、运用技巧并遵循最佳实践,您将能够熟练、高效且安全地在 FreeMarker 模板中使用比较运算符。