前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Java]POI组件基本使用

[Java]POI组件基本使用

原创
作者头像
宇宙无敌暴龙战士之心悦大王
修改2023-03-16 16:00:09
1.3K0
修改2023-03-16 16:00:09
举报
文章被收录于专栏:kwaikwai

Apache POI 基本介绍

Apache POI 是 Apache 软件基金会提供的 100% 开源库。支持 Excel 库的所有基本功能。

image.png | left | 629x536
image.png | left | 629x536

基本概念

在 POI 中,Workbook代表着一个 Excel 文件(工作簿),Sheet代表着 Workbook 中的一个表格,Row 代表 Sheet 中的一行,而 Cell 代表着一个单元格。 HSSFWorkbook对应的就是一个 .xls 文件,兼容 Office97-2003 版本。 XSSFWorkbook对应的是一个 .xlsx 文件,兼容 Office2007 及以上版本。 在 HSSFWorkbook 中,Sheet接口 的实现类为 HSSFSheet,Row接口 的实现类为HSSFRow,Cell 接口的实现类为 HSSFCell。 XSSFWorkbook 中实现类的命名方式类似,在 Sheet、Row、Cell 前加 XSSF 前缀即可。

引入依赖

<!-- 基本依赖,仅操作 xls 格式只需引入此依赖 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.14</version>
</dependency>
<!-- 使用 xlsx 格式需要额外引入此依赖 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.14</version>
</dependency>

使用 POI

使用 POI 的目的就是为了在 Java 中解析/操作 Excel 表格,实现 Excel 的导入/导出的功能,接下来我们依次来看它们的实现代码及注意事项。

导出

导出操作即使用 Java 写出数据到 Excel 中,常见场景是将页面上的数据(可能是经过条件查询的)导出,这些数据可能是财务数据,也可能是商品数据,生成 Excel 后返回给用户下载文件。 该操作主要涉及 Excel 的创建及使用流输出的操作,在 Excel 创建过程中,可能还涉及到单元格样式的操作。

创建并导出基本数据

进行导出操作的第一步是创建 Excel 文件,我们写一个方法,参数是需要写入 Excel 表格的数据和生成 Excel 方式(HSSF,XSSF),返回一个 Workbook 接口对象。 在方法内部我们采用反射来创建 Workbook 的实例对象。

代码

探索阶段,我们先将数据类型限定为 List,并把列数限定为某个数字,生成一个表格。 代码如下:

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;

import java.util.List;
/**
 * Excel 工厂类,负责 Workbook 的生成和解析
 *
 * @author calmer
 * @since 2018/12/5 11:19
 */
public class ExcelFactory {
    /**
     * 构造 Workbook 对象,具体实例化哪种对象由 type 参数指定
     * @param data 要导出的数据
     * @param type Excel 生成方式
     * @return 对应 type 的工作簿实例对象
     * @throws Exception 反射生成对象时出现的异常
     * <li>InstantiationException</li>
     * <li>IllegalAccessException</li>
     * <li>InstantiationException</li>
     */
    public static Workbook createExcel(List data,String type) 
        throws Exception{
        //根据 type 参数生成工作簿实例对象
        Workbook workbook = (Workbook) Class.forName(type).newInstance();
        //这里还可以指定 sheet 的名字
        //Sheet sheet = workbook.createSheet("sheetName");
        Sheet sheet = workbook.createSheet();
        // 限定列数
        int cols = 10;
        int rows = data.size() / cols;
        int index = 0;
        for (int rowNum = 0; rowNum < rows; rowNum++) {
            Row row = sheet.createRow(rowNum);
            for (int colNum = 0; colNum < cols; colNum++) {
                Cell cell = row.createCell(colNum);
                cell.setCellValue(data.get(index++).toString());
            }
        }
        return workbook;
    }
}

调用时,我们生成好数据并构造好 Workbook 对象,再调用 Workbook 的 write(OutputStream stream) 方法生成 Excel 文件。

List<String> strings = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
    strings.add(Integer.toString(i+1));
}
FileOutputStream out = new FileOutputStream("F:\\testXSSF.xlsx");
ExcelFactory.createExcel(strings,"org.apache.poi.xssf.usermodel.XSSFWorkbook").write(out);
out.close();生成结果:
问题

以上代码已经完成简单的 Excel 文件生成操作,但其中还有几点问题没有解决

  • 实际场景下,Excel 表格中可能并不会存 Integer、String 这种基本数据结构的数据,更多的可能是对象数据(JSON、List),需要有表头,并将对象对应的属性一行行的显示出来(参考数据库查询语句执行的结果)。并且表头的样式一定是要控制的。
  • 我们并没有对方法中 type 属性进行限制,即外部可以传来任何类似“a”、“b”这样的无效值,届时程序会抛出异常,可以使用静态常量或枚举类来限定,这样可以增强代码可读性和健壮性。这里我并不想用静态常量或枚举类,打算使用注解的方式来控制参数的有效性。

完善

我们已经明确了两个问题:

  1. 之前的程序并不能在实际场景使用,我们需要将其完善到具有处理实际数据的能力。
  2. 利用注解限定参数的有效性。

我们先来解决第二个问题,即参数的问题。

使用注解限定参数

首先创建一个注解类

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 *
 * @author calmer
 * @since 2018/12/5 12:27
 */
@Retention(RetentionPolicy.SOURCE)
public @interface ExcelType {
    String HSSF = "org.apache.poi.hssf.usermodel.HSSFWorkbook";
    String XSSF = "org.apache.poi.xssf.usermodel.XSSFWorkbook";
}

在方法参数上加上注解

public static Workbook createExcel(List data, @ExcelType String type) throws Exception {
    //内容省略

}

调用时

ExcelFactory.createExcel(list,ExcelType.HSSF).write(out);

关于使用注解来限定参数的取值范围这种方式,我也是偶然看到过,可是这种方式在我这里编译器并不会给任何提示,我对注解了解不够,以后有机会要再好好研究一下。

解决实际数据问题

在实际应用中,很常见的情况是我们有很多实体类,比如 Person,Product,Order 等,借助反射,我们可以获取任意实体类的属性列表、getter 方法,所以目前,我打算利用反射,来处理多个对象的 Excel 导出。 首先我们创建一个方法,用来获取某个对象的属性列表(暂时不考虑要获取父类属性的情况)。

/**
 * 获取对象的属性名数组
 * @param clazz Class 对象,用于获取该类的信息
 * @return 该类的所有属性名数组
 */
private static String[] getFieldsName(Class clazz){
    Field[] fields = clazz.getDeclaredFields();
    String[] fieldNames = new String[fields.length];
    for (int i = 0; i < fields.length; i++) {
        fieldNames[i] = fields[i].getName();
    }
    return fieldNames;
}

然后我们完善 createExcel() 方法

public static Workbook createExcel(List data, @ExcelType String type) throws Exception {
    if(data == null || data.size() == 0){
        throw new Exception("数据不能为空");
    }
    //根据类型生成工作簿
    Workbook workbook = (Workbook) Class.forName(type).newInstance();
    //新建表格
    Sheet sheet = workbook.createSheet();
    //生成表头
    Row thead = sheet.createRow(0);
    String[] fieldsName = getFieldsName(data.get(0).getClass());
    for (int i = 0; i < fieldsName.length; i++) {
        Cell cell = thead.createCell(i);
        cell.setCellValue(fieldsName[i]);
    }
    //保存所有属性的getter方法名
    Method[] methods = new Method[fieldsName.length];
    for (int i = 0; i < data.size(); i++) {
        Row row = sheet.createRow(i+1);
        Object obj = data.get(i);
        for (int j = 0; j < fieldsName.length; j++) {
            //加载第一行数据时,初始化所有属性的getter方法
            if(i == 0){
                String fieldName = fieldsName[j];
                //处理布尔值命名 "isXxx" -> "setXxx"
                if (fieldName.contains("is")) {
                    fieldName = fieldName.split("is")[1];
                }
                methods[j] = obj.getClass().getMethod("get" +
                        fieldName.substring(0,1).toUpperCase() +
                        fieldName.substring(1));
            }
            Cell cell = row.createCell(j);
            Object value = methods[j].invoke(obj);
            //注意判断 value 值是否为空
            if(value == null){
                value = "无";
            }
            cell.setCellValue(value.toString());
        }
    }
    return workbook;
}

感悟

通过这次探索,深知自己不足的地方还很多,原来写代码的时候考虑的太少,有关效率,内存使用等方面的问题在自己测试的时候是看不出来的,真正使用的时候这些问题才会暴露出来,比如某项操作可能会导致用户几十秒甚至几分钟的等待,或者程序直接崩掉。 所以以后还是要小心谨慎,对工具类的使用不能会用就够,要尽量的深入研究。 道可顿悟,事需渐修。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Apache POI 基本介绍
    • 基本概念
      • 引入依赖
      • 使用 POI
        • 导出
          • 创建并导出基本数据
          • 完善
      • 感悟
      相关产品与服务
      Elasticsearch Service
      腾讯云 Elasticsearch Service(ES)是云端全托管海量数据检索分析服务,拥有高性能自研内核,集成X-Pack。ES 支持通过自治索引、存算分离、集群巡检等特性轻松管理集群,也支持免运维、自动弹性、按需使用的 Serverless 模式。使用 ES 您可以高效构建信息检索、日志分析、运维监控等服务,它独特的向量检索还可助您构建基于语义、图像的AI深度应用。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档