在我们日常开发中,经常会遇到读写excel的需求。或者生成一个excel文件给用户,或者读取解析上传的excel文件。
在写过几次读写excel的代码后,我发现其实在读写excel的时候,大部分代码都是一样的,只要具体读写某一行的代码有区别。于是,就打算自己写一个Excel通用读写小工具。以后只需要写一个具体读写行的代码就可以了。这里将具体读/写 excel行 的方法抽象成一个接口,然后在读写excel的通用方法中调用它。使用的时候,只需要传入对应的读/写 的具体实现,和相关的数据/文件就可以了。具体代码如下:
github 地址:https://github.com/wsJava/excel-util
定一个 读和写 Row(行)的接口
读取接口 Reader 和 读取结果ReadResult
读取excel的接口就一个read方法,但返回结果是一个封装过的ReadResult,这个类除了持有读取结果外,还包括下一步操作(如继续读取、退出、跳过该行等),使得调用方能根据自己的需求决定读取方式。注意,如果该方法中抛出异常需要自行处理,结合ReadResult能很容易控制读取流程。
/**
* 读取excel的接口
*/
public interface Reader<T> {
/**
* 读取 excel 行,注意如遇到空行 row 可能为 null, 请自行处理。
* 另外请自行处理方法中抛出的异常,并决定后续操作;若该方法抛出异常,不做任何处理直接向上抛出。
*
* @param row excel 的行对象,可能为 null
* @return ReadResult 持有读取行得到的数据对象,并决定下一步操作,直接退出,跳过该行,继续读,读取本行后退出
*/
ReadResult<T> read(Row row);
}
/**
* 读取的结果
*/
public class ReadResult<T> {
private T value;
private ReadOperatorEnum curOperator;
// 省略一些方法
}
写excel的接口 Writer 和默认实现
Writer包含两个方法,一个用户获取列名,另一个则是将对象写入excel行。getHeader() 默认返回null,即没有列名。并且提供了一个默认的实现,使用字段名作为列名,调用所有定义字段的toString以String格式写入excel。
/**
* 写excel行的接口
*/
public interface Writer<T> {
/**
* 获取表格头部字段,即每列的列名,顺序应该和 write 中的顺序保持一致,默认没有列名。需要列名请重写该方法
*
* @return 列名数组,如果返回 {@code null} 则不会添加列名
*/
default String[] getHeaders() {
return null;
}
/**
* 将对象写入Excel表格的一行中
*
* @param row Excel 行对象
* @param t 需要写入的对象
*/
void write(Row row, T t);
/**
* 提供的一个默认的 Excel writer,将对象所有的定义字段(不包括继承的)写入表格,以字段名为列名,表格内的值为 String类型
*
* @param tClass 数据的类对象
* @param <T> 数据类型
* @return writer
*/
static <T> Writer<T> defaultWriter(Class<T> tClass) {
Objects.requireNonNull(tClass, "tClass must not be null");
final Field[] fields = tClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
}
return new Writer<T>() {
@Override
public String[] getHeaders() {
String[] headers = new String[fields.length];
for (int i = 0; i < fields.length; i++) {
headers[i] = fields[i].getName();
}
return headers;
}
@Override
public void write(Row row, T t) {
String[] headers = getHeaders();
for (int i = 0; i < headers.length; i++) {
Cell cell = row.createCell(i, CellType.STRING);
try {
cell.setCellValue(String.valueOf(fields[i].get(t)));
} catch (IllegalAccessException ignored) {
}
}
}
};
}
}
ExcelUtil 通用工具类
这主要提供两个功能,读取excel获取list 对象;将list对象写入excel。这里只贴出核心的处理流程。具体代码可以看我的github仓库;
public static <T> List<T> readExcel(Workbook workbook, int sheetIndex, int startRow, Reader<T> reader) {
Objects.requireNonNull(workbook, "workbook must not be null");
Objects.requireNonNull(reader, "reader must not be null");
Sheet sheet = workbook.getSheetAt(sheetIndex);
List<T> list = new ArrayList<>(sheet.getLastRowNum());
for (int rowIndex = startRow; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
Row row = sheet.getRow(rowIndex);
ReadResult<T> result = reader.read(row);
ReadOperatorEnum curOperator = result.curOperator();
switch (curOperator) {
case CONTINUE:
list.add(result.get());
break;
case SKIP:
break;
case ADD_EXIT:
list.add(result.get());
case EXIT:
return list;
}
}
return list;
}
public static <T> void writeExcelToWorkbook(List<T> data, Workbook workbook, int sheetIndex, int startRow, Writer<T> writer) {
Objects.requireNonNull(data, "data must not be null");
Objects.requireNonNull(workbook, "workbook must not be null");
Objects.requireNonNull(writer, "writer must not be null");
Sheet sheet = getOrCreateSheet(workbook, sheetIndex);
String[] headers = writer.getHeaders();
if (headers != null) {
Row header = sheet.createRow(startRow++);
for (int i = 0; i < headers.length; i++) {
Cell cell = header.createCell(i);
cell.setCellValue(headers[i]);
}
}
for (T t : data) {
Row row = sheet.createRow(startRow++);
writer.write(row, t);
}
}
使用示例
这里使用上面的工具,先生成一个excel文件,然后再从excel中读取数据。
public class ExcelUtilTest{
public static void main(String[] args) throws IOException {
// ExcelUtil.addDataToExcelFile(getStudent(), new File("student.xlsx"), 2, 9, Writer.defaultWriter(Student.class));
File file = ExcelUtil.writeNewExcel(getStudent(), "student.xlsx", Writer.defaultWriter(Student.class));
// 有需要可以在这里加一个异常捕获
List<Student> students = ExcelUtil.readExcel(file, 0, 1, ExcelUtilTest::readStudent);
System.out.println(students);
}
private static ReadResult<Student> readStudent(Row row) {
if (row == null) {
return ReadResult.skip();
}
// 读取到第6行就退出
if (row.getRowNum() > 5) {
return ReadResult.exit();
}
// 如果id读取报错,就说明excel格式不正确,捕获异常,添加相关信息,继续抛出
Integer id;
try {
id = ExcelReadUtils.getIntFromCellDefaultNull(row.getCell(0));
} catch (Exception e) {
throw new RuntimeException("excel 读取异常,当前行号:"+row.getRowNum(), e);
}
// 姓名这格不存在就默认null
String s = ExcelReadUtils.getStrFromCellDefaultNull(row.getCell(1));
return ReadResult.add(new Student(id, s));
}
private static List<Student> getStudent() {
List<Student> list = new ArrayList<>(100);
for (int i = 0; i < 10; i++) {
list.add(new Student(i, "stu-"+i));
}
return list;
}
static class Student{
Integer id;
String name;
public Student(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
}