做数据处理的朋友应该都遇到过这种场景:需要批量生成带公式的Excel报表,或者读取现有表格中的公式进行二次计算。以前我都是手动在Excel里写公式,后来发现用Java代码来处理更高效,尤其是数据量大的时候。
今天整理一下平时用得比较多的几种Excel公式处理方式,希望能给有同样需求的朋友一些参考。
环境准备
使用的库
本文示例使用的是 Spire.XLS for Java,这是一个专门处理Excel文件的Java库。如果你项目中已经在用Apache POI,也可以实现类似功能,不过API会有些不同。
安装方式
Maven项目 ,在 pom.xml 中添加依赖:
xml
<repositories>
<repository>
<id>com.e-iceblue</id>
<name>e-iceblue</name>
<url>https://repo.e-iceblue.cn/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>e-iceblue</groupId>
<artifactId>spire.xls</artifactId>
<version>14.12.0</version>
</dependency>
</dependencies>
Gradle项目 ,在 build.gradle 中添加:
gradle
repositories {
maven {
url 'https://repo.e-iceblue.cn/repository/maven-public/'
}
}
dependencies {
implementation 'e-iceblue:spire.xls:14.12.0'
}
或者直接下载JAR包从官网导入项目。
一、最基础的:写入和读取公式
1. 写入常见公式
最常见的就是往单元格里写公式了。比如SUM、AVERAGE这些统计函数:
java
import com.spire.xls.*;
public class WriteFormulas {
public static void main(String[] args) {
// 创建工作簿
Workbook workbook = new Workbook();
// 获取第一个工作表
Worksheet sheet = workbook.getWorksheets().get(0);
// 准备测试数据
sheet.getCellRange("B2").setNumberValue(7.3);
sheet.getCellRange("C2").setNumberValue(5);
sheet.getCellRange("D2").setNumberValue(8.2);
// 写入求和公式
sheet.getCellRange("E2").setFormula("=SUM(B2:D2)");
// 写入平均值公式
sheet.getCellRange("F2").setFormula("=AVERAGE(B2:D2)");
// 保存文件
workbook.saveToFile("result.xlsx", ExcelVersion.Version2013);
// 释放资源
workbook.dispose();
System.out.println("文件已生成!");
}
}
注意一个小细节:如果想在单元格里显示公式文本而不是计算结果,需要在前面加个单引号:
java
// 这样单元格会显示 "=SUM(B2:D2)" 这个文本
sheet.getCellRange("A2").setText("'=SUM(B2:D2)");
2. 读取已有公式
有时候需要读取现成Excel文件里的公式,看看是怎么计算的:
java
Workbook workbook = new Workbook();
workbook.loadFromFile("existing.xlsx");
Worksheet sheet = workbook.getWorksheets().get(0);
// 获取公式字符串
String formula = sheet.getCellRange("C14").getFormula();
System.out.println("公式: " + formula);
// 获取公式计算结果
double value = sheet.getCellRange("C14").getFormulaNumberValue();
System.out.println("结果: " + value);
这个功能在做公式审计或者模板分析时特别有用。
二、跨工作表引用
实际项目中经常需要跨Sheet引用数据,写法跟Excel里一样:
java
// 引用Sheet1的B3单元格
sheet.getCellRange("A1").setFormula("=Sheet1!$B$3");
// 引用某个区域的平均值
sheet.getCellRange("A2").setFormula("=AVERAGE(Sheet1!$D$3:G$3)");
绝对引用(带$符号)和相对引用的区别一定要搞清楚,不然复制公式时容易出错。
三、日期和时间函数
处理时间相关的报表时,这几个函数很实用:
java
// 当前日期时间
sheet.getCellRange("A1").setFormula("=NOW()");
sheet.getCellRange("A1").getCellStyle().setNumberFormat("yyyy-MM-DD HH:mm:ss");
// 提取年、月、日
sheet.getCellRange("B1").setFormula("=YEAR(TODAY())");
sheet.getCellRange("C1").setFormula("=MONTH(TODAY())");
sheet.getCellRange("D1").setFormula("=DAY(TODAY())");
// 提取时、分、秒
sheet.getCellRange("E1").setFormula("=HOUR(NOW())");
sheet.getCellRange("F1").setFormula("=MINUTE(NOW())");
sheet.getCellRange("G1").setFormula("=SECOND(NOW())");
// 星期几
sheet.getCellRange("H1").setFormula("=WEEKDAY(TODAY())");
NOW()函数每次打开文件都会重新计算,如果需要固定时间,建议计算后转成静态值。
四、数学和统计函数
除了基本的SUM、AVERAGE,还有一些常用的:
java
// 最大值、最小值
sheet.getCellRange("A1").setFormula("=MAX(10,30,50)");
sheet.getCellRange("A2").setFormula("=MIN(5,7,3)");
// 四舍五入
sheet.getCellRange("A3").setFormula("=ROUND(3.14159, 2)"); // 结果: 3.14
// 取整
sheet.getCellRange("A4").setFormula("=INT(9.8)"); // 结果: 9
// 绝对值
sheet.getCellRange("A5").setFormula("=ABS(-15.6)"); // 结果: 15.6
// 平方根
sheet.getCellRange("A6").setFormula("=SQRT(144)"); // 结果: 12
// 随机数
sheet.getCellRange("A7").setFormula("=RAND()"); // 0-1之间的随机数
五、逻辑函数
条件判断在报表中很常见:
java
// IF函数
sheet.getCellRange("A1").setFormula("=IF(B1>60, \"及格\", \"不及格\")");
// AND、OR
sheet.getCellRange("A2").setFormula("=AND(B1>60, C1>60)");
sheet.getCellRange("A3").setFormula("=OR(B1>90, C1>90)");
// NOT
sheet.getCellRange("A4").setFormula("=NOT(TRUE)"); // 结果: FALSE
实际应用:用IF嵌套来做成绩等级划分:
java
"=IF(A1>=90, \"优秀\", IF(A1>=80, \"良好\", IF(A1>=60, \"及格\", \"不及格\")))"
六、文本函数
处理字符串时也少不了公式:
java
// 字符串长度
sheet.getCellRange("A1").setFormula("=LEN(\"Hello World\")"); // 结果: 11
// 截取子串
sheet.getCellRange("A2").setFormula("=MID(\"Hello World\", 7, 5)"); // 结果: World
// 类型转换
sheet.getCellRange("A3").setFormula("=VALUE(\"123\")"); // 文本转数字
七、数组公式(高级用法)
数组公式可以一次性对多个值进行计算,适合复杂的数据分析:
java
// 准备数据
sheet.getCellRange("A1").setNumberValue(1);
sheet.getCellRange("A2").setNumberValue(2);
sheet.getCellRange("A3").setNumberValue(3);
sheet.getCellRange("B1").setNumberValue(4);
sheet.getCellRange("B2").setNumberValue(5);
sheet.getCellRange("B3").setNumberValue(6);
// 设置数组公式(线性回归)
sheet.getCellRange("A5:C6").setFormulaArray("=LINEST(A1:A3,B1:B3,TRUE,TRUE)");
// 计算公式值
workbook.calculateAllValue();
使用场景:财务分析、统计建模时会用到这类高级函数。
八、不依赖Excel直接计算公式值
有时候不需要生成Excel文件,只是想算个公式的结果,可以直接计算:
java
Workbook workbook = new Workbook();
// 直接计算公式的值
Object result1 = workbook.calculateFormulaValue("=10+20*3");
System.out.println(result1); // 结果: 70
Object result2 = workbook.calculateFormulaValue("=SUM(1,2,3,4,5)");
System.out.println(result2); // 结果: 15
// 甚至可以引用单元格
workbook.getWorksheets().get(0).getCellRange("A1").setNumberValue(100);
Object result3 = workbook.calculateFormulaValue("=A1*2");
System.out.println(result3); // 结果: 200
这个功能在做快速计算或者公式验证时很方便,不用真的创建Excel文件。
九、移除公式保留计算结果
有个实际需求:给客户发报表时,只想给他们看最终数据,不想暴露计算公式。这时候可以把公式转成静态值:
java
Workbook workbook = new Workbook();
workbook.loadFromFile("with_formulas.xlsx");
for (Worksheet sheet : (Iterable<Worksheet>) workbook.getWorksheets()) {
for (CellRange cell : (Iterable<CellRange>) sheet.getRange()) {
if (cell.hasFormula()) {
// 获取公式计算结果
Object value = cell.getFormulaValue();
// 清除公式
cell.clear(ExcelClearOptions.ClearContent);
// 设置为静态值
cell.setValue(value.toString());
}
}
}
workbook.saveToFile("without_formulas.xlsx", ExcelVersion.Version2013);
应用场景:财务报表、对外发布的统计数据等需要保护公式逻辑的场景。
十、Excel 2013+的新函数
新版Excel增加了一些实用函数,比如位运算、URL编码等:
java
// 位运算
sheet.getCellRange("A1").setFormula("=BITOR(23,10)"); // 按位或
sheet.getCellRange("A2").setFormula("=BITAND(23,10)"); // 按位与
sheet.getCellRange("A3").setFormula("=BITLSHIFT(23,2)"); // 左移
sheet.getCellRange("A4").setFormula("=BITRSHIFT(23,2)"); // 右移
// URL编码
sheet.getCellRange("A5").setFormula("=ENCODEURL(\"https://example.com\")");
// ISO周数
sheet.getCellRange("A6").setFormula("=ISOWEEKNUM(DATE(2024,1,1))");
// 精确舍入
sheet.getCellRange("A7").setFormula("=CEILING.PRECISE(-4.6, 3)");
sheet.getCellRange("A8").setFormula("=FLOOR.MATH(12.758, 2, -1)");
这些函数在处理特定业务逻辑时很有用,比如网络应用开发中的URL处理。
十一、命名范围中使用公式
如果公式里用到的区域经常变化,可以用命名范围来简化:
java
// 定义命名范围
Name name = workbook.getNameList().add("SalesData");
name.setRefersToRange(sheet.getCellRange("A1:A100"));
// 在公式中使用命名范围
sheet.getCellRange("B1").setFormula("=SUM(SalesData)");
这样做的好处是,当数据区域扩展时,只需要修改命名范围的定义,不用改所有公式。
十二、R1C1引用样式
除了常见的A1样式,Excel还支持R1C1引用方式(行号+列号):
java
// R1C1样式的公式
sheet.getCellRange("C3").setR1C1Formula("=R[-1]C[-1]+R[-1]C[0]");
// 意思是:上一行左边一格 + 上一行当前列
// 数组形式的R1C1公式
sheet.getCellRange("D3:E4").setR1C1FormulaArray("=R[-2]C[-3]:R[-1]C[-2]");
什么时候用:在程序化生成公式时,R1C1方式更容易通过坐标计算来动态构建公式。
十三、自定义函数(加载项函数)
如果遇到Excel内置函数不够用的情况,可以注册自定义函数:
java
// 注册加载项函数库
workbook.registerAddInFunction("MyFunctions.xll");
// 然后就可以像普通函数一样使用
sheet.getCellRange("A1").setFormula("=MYCUSTOMFUNC(B1,C1)");
适用场景:有特殊计算需求的行业,比如金融衍生品定价、工程计算等。
十四、SUBTOTAL函数(忽略隐藏行)
做数据筛选时,普通的SUM会把隐藏行也算进去,用SUBTOTAL可以避免这个问题:
java
// 第一个参数3表示COUNTA,只统计可见单元格
sheet.getCellRange("A1").setFormula("=SUBTOTAL(3, B2:E100)");
常用功能代码:
- 1: AVERAGE
- 2: COUNT
- 3: COUNTA
- 9: SUM
- 109: SUM(忽略隐藏值)
十五、实际项目中的综合应用
最后分享一个实际场景:生成月度销售报表。
java
Workbook workbook = new Workbook();
Worksheet sheet = workbook.getWorksheets().get(0);
// 1. 写入标题
sheet.getCellRange("A1").setValue("月份");
sheet.getCellRange("B1").setValue("销售额");
sheet.getCellRange("C1").setValue("成本");
sheet.getCellRange("D1").setValue("利润");
sheet.getCellRange("E1").setValue("利润率");
// 2. 写入数据并添加公式
for (int i = 2; i <= 13; i++) {
sheet.getCellRange("A" + i).setValue((i-1) + "月");
sheet.getCellRange("B" + i).setNumberValue(Math.random() * 100000);
sheet.getCellRange("C" + i).setNumberValue(Math.random() * 60000);
// 利润 = 销售额 - 成本
sheet.getCellRange("D" + i).setFormula("=B" + i + "-C" + i);
// 利润率 = 利润 / 销售额
sheet.getCellRange("E" + i).setFormula("=D" + i + "/B" + i);
sheet.getCellRange("E" + i).getCellStyle().setNumberFormat("0.00%");
}
// 3. 添加汇总行
int lastRow = 14;
sheet.getCellRange("A" + lastRow).setValue("合计");
sheet.getCellRange("B" + lastRow).setFormula("=SUM(B2:B13)");
sheet.getCellRange("C" + lastRow).setFormula("=SUM(C2:C13)");
sheet.getCellRange("D" + lastRow).setFormula("=SUM(D2:D13)");
sheet.getCellRange("E" + lastRow).setFormula("=AVERAGE(E2:E13)");
// 4. 设置格式
sheet.getAllocatedRange().autoFitColumns();
sheet.getCellRange("A1:E1").getCellStyle().getExcelFont().isBold(true);
workbook.saveToFile("monthly_report.xlsx", ExcelVersion.Version2013);
这样一个完整的报表就生成了,所有计算都是通过公式完成的,后续修改原始数据,结果会自动更新。
小结
总结一下几个关键点:
- 简单公式直接用
setFormula(),跟Excel里写法一致 - 跨表引用 用
SheetName!CellAddress格式 - 数组公式 用
setFormulaArray(),记得调用calculateAllValue() - 只要计算结果不要公式,遍历单元格转换
- 直接计算公式值 用
calculateFormulaValue(),不用创建文件 - 新函数如位运算、URL编码等,注意Excel版本兼容性
实际使用中,最重要的是理解业务需求,选择合适的公式类型。不是所有场景都需要复杂的公式,有时候简单的SUM、IF就能解决问题。
希望这些经验对大家有帮助,如果有其他好用的技巧,欢迎交流!