Java 读写 Excel 公式:从基础到高级的实战总结

做数据处理的朋友应该都遇到过这种场景:需要批量生成带公式的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);

这样一个完整的报表就生成了,所有计算都是通过公式完成的,后续修改原始数据,结果会自动更新。

小结

总结一下几个关键点:

  1. 简单公式直接用setFormula(),跟Excel里写法一致
  2. 跨表引用SheetName!CellAddress格式
  3. 数组公式setFormulaArray(),记得调用calculateAllValue()
  4. 只要计算结果不要公式,遍历单元格转换
  5. 直接计算公式值calculateFormulaValue(),不用创建文件
  6. 新函数如位运算、URL编码等,注意Excel版本兼容性

实际使用中,最重要的是理解业务需求,选择合适的公式类型。不是所有场景都需要复杂的公式,有时候简单的SUM、IF就能解决问题。

希望这些经验对大家有帮助,如果有其他好用的技巧,欢迎交流!

相关推荐
wb043072015 小时前
Java 26
java·开发语言
白露与泡影5 小时前
JVM GC调优实战:从线上频繁Full GC到RT降低80%的全过程
java·开发语言·jvm
灰灰勇闯IT5 小时前
pyasc:用 Python 调用 CANN 的推理能力
开发语言·python
范什么特西5 小时前
Spring 动态代理 静态代理
java·后端·spring
醇氧5 小时前
Spring 动态注册 Bean 深度解析:从源码到实践
java·后端·spring
笨拙的老猴子5 小时前
[特殊字符] Java GC机制详解:G1、ZGC、Shenandoah全面解析与版本演进对比
java·开发语言
水木流年追梦5 小时前
大模型入门-Reward 奖励模型训练
开发语言·python·算法·leetcode·正则表达式
电子云与长程纠缠6 小时前
UE5制作六边形包裹球体效果
开发语言·python·ue5
砍材农夫6 小时前
物联网 基于netty构建mqtt协议规范(遗嘱与保留消息)
java·开发语言·物联网·netty