1. 核心概念
FreeMarker 的算术运算符允许在模板中对数字进行基本的数学运算。这些运算符在处理动态数据、计算价格、分页、统计等场景中非常有用。
主要算术运算符
运算符 | 描述 | 示例 |
---|---|---|
+ |
加法 | ${a + b} |
- |
减法 | ${a - b} |
* |
乘法 | ${a * b} |
/ |
除法(结果为浮点数) | ${a / b} |
% |
取模(求余数) | ${a % b} |
++ |
自增(前缀或后缀) | ${a++} 或 ${++a} |
-- |
自减(前缀或后缀) | ${a--} 或 ${--a} |
数据类型支持
- FreeMarker 支持对
number
类型进行算术运算。 - 字符串、布尔值等非数字类型参与运算会导致错误。
null
值参与运算会抛出异常。
2. 操作步骤(非常详细)
以下是使用 FreeMarker 算术运算符的详细步骤,帮助您快速掌握并实践。
步骤 1:准备开发环境
引入 FreeMarker 依赖:
- 如果使用 Maven,在
pom.xml
中添加:<dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.32</version> <!-- 推荐使用最新稳定版 --> </dependency>
- 如果使用 Gradle,在
build.gradle
中添加:implementation 'org.freemarker:freemarker:2.3.32'
- 如果使用 Maven,在
创建模板文件:
- 在
src/main/resources/templates
目录下创建一个.ftl
文件,例如arithmetic.ftl
。
- 在
步骤 2:定义数据模型(Java 代码)
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class ArithmeticExample {
public static void main(String[] args) throws IOException, TemplateException {
// 1. 创建 FreeMarker 配置实例
Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
// 2. 设置模板加载路径(这里是类路径)
cfg.setClassForTemplateLoading(ArithmeticExample.class, "/templates");
// 3. 获取模板
Template template = cfg.getTemplate("arithmetic.ftl");
// 4. 创建数据模型(Map)
Map<String, Object> dataModel = new HashMap<>();
dataModel.put("price", 100.0); // 商品价格
dataModel.put("taxRate", 0.08); // 税率 8%
dataModel.put("quantity", 5); // 数量
dataModel.put("discount", 10.0); // 折扣金额
dataModel.put("shipping", 5.5); // 运费
// 5. 创建输出流(例如,输出到控制台或文件)
Writer out = new OutputStreamWriter(System.out);
// 或者写入文件
// Writer out = new FileWriter(new File("output.html"));
// 6. 合并模板和数据模型,生成输出
template.process(dataModel, out);
// 7. 刷新并关闭输出流
out.flush();
out.close();
}
}
步骤 3:编写模板文件(arithmetic.ftl
)
<#-- 算术运算符示例 -->
<h1>订单详情</h1>
<#-- 1. 加法 (+) -->
<p>商品单价: ${price} 元</p>
<p>运费: ${shipping} 元</p>
<#-- 计算含运费总价 -->
<p>含运费总价: ${(price + shipping)?string.currency} 元</p>
<#-- 2. 减法 (-) -->
<p>原价: ${price} 元</p>
<p>折扣: ${discount} 元</p>
<#-- 计算折后价 -->
<p>折后价: ${(price - discount)?string.currency} 元</p>
<#-- 3. 乘法 (*) -->
<p>单价: ${price} 元</p>
<p>数量: ${quantity}</p>
<#-- 计算小计 -->
<p>小计: ${(price * quantity)?string.currency} 元</p>
<#-- 4. 除法 (/) -->
<#-- 计算平均每件商品价格 -->
<p>平均每件价格: ${(price * quantity / quantity)?string.currency} 元</p>
<#-- 注意:除法结果默认为浮点数 -->
<#-- 5. 取模 (%) -->
<#-- 假设需要判断数量是否为偶数 -->
<#if (quantity % 2) == 0>
<p>购买数量 ${quantity} 是偶数。</p>
<#else>
<p>购买数量 ${quantity} 是奇数。</p>
</#if>
<#-- 6. 复杂表达式 -->
<#-- 计算最终总价: (单价 * 数量 - 折扣 + 运费) * (1 + 税率) -->
<#assign subtotal = price * quantity>
<#assign beforeTaxTotal = subtotal - discount + shipping>
<#assign finalTotal = beforeTaxTotal * (1 + taxRate)>
<p>最终总价: ${finalTotal?string.currency} 元</p>
<#-- 7. 自增/自减 (通常在循环中使用) -->
<#-- 注意:FreeMarker 模板中直接修改变量较少见,更多在循环中体现 -->
<#list 1..3 as i>
<p>第 ${i} 次循环</p>
<#-- 模拟计数器递增 -->
<#assign counter = i />
<p>计数器值: ${counter}</p>
</#list>
步骤 4:运行程序并查看结果
- 编译并运行
ArithmeticExample.java
。 - 查看控制台输出或生成的 HTML 文件,验证算术运算结果是否正确。
3. 常见错误
类型不匹配错误:
- 错误:
${"abc" + 5}
或${null + 10}
。 - 原因:字符串
"abc"
或null
不能直接参与算术运算。 - 解决:确保参与运算的变量是数字类型。使用
??
操作符处理可能为null
的值,例如:${(price??0) + (shipping??0)}
。
- 错误:
除零错误:
- 错误:
${10 / 0}
。 - 原因:除以零在数学上无定义。
- 解决:在执行除法前检查除数是否为零。例如:
<#if divisor != 0> ${numerator / divisor} <#else> 除数不能为零 </#if>
- 错误:
精度问题(浮点数):
- 问题:浮点数运算可能存在精度损失,例如
0.1 + 0.2
可能不精确等于0.3
。 - 解决:对于货币等需要高精度的计算,建议在 Java 后端使用
BigDecimal
完成,然后将结果传递给模板。或者在模板中使用?round
、?floor
、?ceiling
等内建函数控制显示精度,但这不改变计算精度本身。
- 问题:浮点数运算可能存在精度损失,例如
运算符优先级混淆:
- 问题:
${a + b * c}
与${(a + b) * c}
结果不同。 - 解决:明确使用括号
()
来控制运算顺序,避免歧义。
- 问题:
4. 注意事项
- 变量作用域:使用
<#assign>
定义的变量在当前宏、函数或模板范围内有效。注意变量命名冲突。 null
值处理:FreeMarker 中null
值参与算术运算会抛出异常。务必使用??
操作符提供默认值。- 性能考量:避免在模板中进行过于复杂的计算。尽量将复杂的业务逻辑放在 Java 代码中处理,模板只负责展示。
- 类型转换:FreeMarker 会尝试进行一些隐式类型转换(如字符串转数字),但不保证成功。最好确保数据模型提供正确的类型。
- 自增/自减的限制:FreeMarker 模板语言更侧重于展示,直接修改变量值(如
++
、--
)的场景相对较少,通常在循环计数或临时变量赋值时使用<#assign>
更清晰。
5. 使用技巧
使用
<#assign>
简化复杂表达式:<#assign basePrice = item.price * item.quantity> <#assign total = (basePrice - discount) * (1 + taxRate)> <p>总计: ${total?string.currency}</p>
这比在单个表达式中写长串运算更易读。
结合内建函数进行格式化:
?string.currency
:格式化为货币。?string.number
:格式化为普通数字。?string.computer
:使用计算机格式(无千位分隔符)。?round
,?floor
,?ceiling
:四舍五入、向下取整、向上取整。- 示例:
${(price * quantity)?string.currency}
条件判断结合运算:
<#if (totalAmount > 1000)> <p>恭喜!您获得了 VIP 折扣!</p> <#assign finalAmount = totalAmount * 0.9> <#else> <#assign finalAmount = totalAmount> </#if>
利用宏(Macro)封装常用计算:
<#macro calculateTax amount rate> <#return amount * rate> </#macro> <#-- 使用宏 --> <p>税费: ${calculateTax(subtotal, taxRate)?string.currency}</p>
6. 最佳实践与性能优化
逻辑前置(推荐):
- 最佳实践:将复杂的业务逻辑、数据计算和聚合操作放在 Java 服务层完成。FreeMarker 模板应专注于数据展示和简单的格式化逻辑。
- 优点:提高模板可读性,便于维护和测试;计算性能通常优于在模板引擎中执行;避免模板中出现大量复杂表达式。
缓存模板:
- FreeMarker 的
Configuration
对象会自动缓存已加载的模板(基于文件修改时间)。确保正确配置Configuration
(如设置合理的incompatibleImprovements
版本和模板加载器),以充分利用缓存,避免重复解析模板文件。
- FreeMarker 的
避免在循环中进行昂贵计算:
- 如果必须在
#list
循环中进行计算,尽量将循环外不变的计算提取到循环外,或者预先计算好结果。
- 如果必须在
使用适当的数据类型:
- 后端传递给模板的数据应尽量使用精确的数字类型(如
Integer
,Long
,Double
,BigDecimal
),避免传递需要在模板中解析的字符串数字。
- 后端传递给模板的数据应尽量使用精确的数字类型(如
最小化模板中的脚本逻辑:
- 虽然 FreeMarker 支持
<#assign>
、<#if>
等,但应尽量减少模板中的“编程”逻辑。保持模板的声明式特性,使其更易于前端开发人员理解和修改。
- 虽然 FreeMarker 支持
性能监控:
- 在高并发场景下,关注模板渲染时间。如果发现瓶颈,优先检查是否在模板中执行了过多或过重的计算,并考虑将其移至后端。
通过以上系统性的介绍,您应该能够快速掌握 FreeMarker 算术运算符的核心知识,并在实践中有效应用,同时避免常见错误,遵循最佳实践以构建高效、可维护的模板。