废话不多说上代码!
maven
注意:5.1.0和5.2.0版本的优点问题,目前只能暂时先使用5.0.0版本
<!--Excel处理-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.0.0</version>
</dependency>
Excel Cell注解:
package com.excel;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* excel单元格
* @author YanZhen
* @date 2022/01/25 14:27
*/
@Target(FIELD)
@Retention(RUNTIME)
public @interface ExcelCell {
/**
* 列名,与Excel中的标题对应
* @return 名称
*/
String title() default "";
/**
* 目前只支持日期类型(Date、LocalDateTime)
* 默认:yyyy-MM-dd HH:mm:ss
*
* @return 字段编号
*/
String fieldFormat() default "";
}
Excel Util:
package com.excel;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.apache.commons.lang3.StringUtils;
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 org.apache.poi.ss.usermodel.WorkbookFactory;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
/**
* Excel解析工具类。
* 目前可解析sheet的第一行为标题,且所有sheet的第一行的标题字段都相同,标题字段位置可以不同
*
* @author YanZhen
* @date 2022/01/25 11:45
*/
@Slf4j
public class ExcelUtil {
/**
* 标题对应的字段信息
*/
private static Map<String, Field> titleFields;
/**
* 字段的格式
*/
private static final Map<String, String> fieldFormat = new HashMap<>();
/**
* 解析Excel数据转换为指定对象
*
* @param inputStream Excel文件流。注意:方法中会关闭流,所以不能重复使用
* @param <T> clazz 转换的对象类型
* @return 转换后的数据
*/
public static <T> List<T> getSheetToObject(InputStream inputStream, Class<T> clazz) {
// 解析类的标题和字段的对应关系
titleFields = getTitleField(clazz);
if (titleFields.isEmpty()) {
return null;
}
// sheet数据
return getSheet(inputStream, clazz);
}
/**
* 解析Excel的第一个sheet的数据,并转换为指定对象
*
* @param inputStream Excel文件流。注意:方法中会关闭流,所以不能重复使用
* @param <T> clazz 要转换的对象类型
* @return 解析后的对象数据
*/
public static <T> List<List<T>> getSheetListToObject(InputStream inputStream, Class<T> clazz) {
// 解析类的标题和字段的对应关系
titleFields = getTitleField(clazz);
if (titleFields.isEmpty()) {
return null;
}
// sheet数据,转换对象
return getSheetList(inputStream, clazz);
}
/**
* Sheet
* 获取sheet的数据
*
* @param inputStream Excel文件流
* @param <T> clazz 要转换的对象类型
* @return 解析后的对象数据
*/
@Nullable
private static <T> List<T> getSheet(InputStream inputStream, Class<T> clazz) {
// 获取所有的sheet信息
Workbook sheets = getWorkbook(inputStream);
if (sheets == null) return null;
// 只获取第一个sheet
Sheet sheet = sheets.getSheetAt(0);
if (sheet == null) {
return null;
}
// 行数据
return getRows(clazz, sheet);
}
/**
* Sheet
* 获取sheet的数据
*
* @param inputStream Excel文件流
* @param <T> clazz 要转换的对象类型
* @return 解析后的对象数据
*/
@Nullable
private static <T> List<List<T>> getSheetList(InputStream inputStream, Class<T> clazz) {
// 想要得到的数据
List<List<T>> sheetList = new ArrayList<>();
Workbook sheets = getWorkbook(inputStream);
if (sheets == null) return null;
// sheet
for (Sheet sheet : sheets) {
if (sheet == null) {
continue;
}
// 行数据
List<T> rows = getRows(clazz, sheet);
if (rows == null) return null;
// sheet数据
sheetList.add(rows);
}
return sheetList;
}
/**
* 获取Excel的所有sheet的数据
*
* @param inputStream Excel文件流
* @return sheet对象
*/
@Nullable
private static Workbook getWorkbook(InputStream inputStream) {
Workbook sheets;
// 解析Excel数据
try {
sheets = WorkbookFactory.create(inputStream);
inputStream.close();
} catch (IOException e) {
log.error("解释Excel异常", e);
return null;
}
return sheets;
}
/**
* Row
* 获取行数据
*
* @param <T> clazz 对象类型
* @param sheet sheet中的信息
* @return 行数据
*/
@Nullable
private static <T> List<T> getRows(Class<T> clazz, Sheet sheet) {
// 标题对应索引
Map<String, Integer> titleIndexMap = new HashMap<>();
// 行信息
List<T> rows = new ArrayList<>();
// 行
for (Row cells : sheet) {
if (cells == null) {
continue;
}
// 根据行标,解析第一行为标题
if (cells.getRowNum() == 0) {
titleIndexMap = getTitle(cells);
continue;
}
// 不能没有标题,否则无法转对应的数据
if (titleIndexMap.isEmpty()) {
return Lists.newArrayList();
}
// 初始化对象信息
T t;
try {
t = clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
log.error("初始化对象信息异常!", e);
return null;
}
// 根据标题对应的索引信息,解析单元格数据
titleIndexMap.forEach((title, cellNum) -> {
// 获取单元格的信息
Cell cell = cells.getCell(cellNum);
// 根据对象的字段信息赋值
titleFields.forEach((title1, field) -> {
// 标题是否匹配
if (!Objects.equals(title, title1)) {
return;
}
// 为对象字段赋值
try {
initObject(t, cell, field);
} catch (IllegalAccessException e) {
log.error("赋值对象的{}值发生异常!", field.getName(), e);
}
});
});
// 行数据
rows.add(t);
}
return rows;
}
/**
* 初始化对象信息
*
* @param t 对象实例
* @param cell 单元格信息
* @param field 对象字段信息
* @param <T> 对象类型
*/
private static <T> void initObject(T t, Cell cell, Field field) throws IllegalAccessException {
// 单元格的值
Object value = getCellValue(cell, field);
// 允许私有字段赋值
field.setAccessible(true);
// 字段赋值
field.set(t, value);
}
/**
* 获取单元格值
*
* @param cell 单元格信息
* @param field 字段信息
* @return 单元格值
*/
@Nullable
private static Object getCellValue(Cell cell, Field field) {
if (cell == null) {
return null;
}
// 获取字段的类型
Class<?> type = field.getType();
Object value = null;
/*
* 默认日期格式类型
*/
String defaultDateFormat = "yyyy-MM-dd HH:mm:ss";
// String
if (type.equals(String.class)) {
value = cell.toString();
// 数值类型
} else if (type.getSuperclass().equals(Number.class)) {
value = getObjToNumber(cell, type);
// boolean
} else if (type.equals(Boolean.class)) {
value = cell.getBooleanCellValue();
// 日期类型
} else if (type.equals(Date.class)) {
try {
// 日期字段格式
String format = fieldFormat.getOrDefault(field.getName(), defaultDateFormat);
value = new SimpleDateFormat(format).parse(cell.toString());
} catch (ParseException e) {
log.error("{}字段类型({}),日期格式化失败!", field.getName(), type, e);
}
} else if (type.equals(LocalDateTime.class)) {
// 日期字段格式
String format = fieldFormat.getOrDefault(field.getName(), defaultDateFormat);
value = LocalDateTime.parse(cell.toString(), DateTimeFormatter.ofPattern(format));
// 没有匹配到的情况
} else {
log.warn("{}字段类型({})没有匹配到,忽略赋值。单元格实际值为:{}", field.getName(), type, cell.getStringCellValue());
}
return value;
}
/**
* 解析单元格值
* @param cell 单元格信息
* @param type 目标类型
* @return 解析后的对应类型的值
*/
private static Object getObjToNumber(Cell cell, Class<?> type) {
// 空时,默认返回空
if (cell == null || type == null) {
return null;
}
// 单元格值转字符串
String s = cell.toString();
if (StringUtils.isBlank(s)) {
return null;
}
// 去两边空格
s = s.trim();
// 数值类型校验
if (!Pattern.matches("^-?(\\d+).?(\\d+)?$", s)) {
throw new IllegalArgumentException(String.format(
"格式错误,单元格值(%s)为非数值类型,不能赋值给数值类型(%s)的对象", s, type.getName()));
}
// 类型匹配
if (type.equals(Double.class) || type.equals(double.class)) {
return Double.parseDouble(s);
} else if (type.equals(Float.class) || type.equals(float.class)) {
return Float.parseFloat(s);
} else if (type.equals(Integer.class) || type.equals(int.class)) {
return Integer.parseInt(s);
} else if (type.equals(Short.class) || type.equals(short.class)) {
return Short.parseShort(s);
} else if (type.equals(Byte.class) || type.equals(byte.class)) {
return Byte.parseByte(s);
} else {
log.warn("暂时不支持复杂类型({})的转化,值转化失败:{}", type.getName(), s);
return null;
}
}
/**
* 解析标题名和字段的对应关系
*
* @param clazz 对象
* @param <T> 对象类型
* @return 标题名和字段名
*/
private static <T> Map<String, Field> getTitleField(Class<T> clazz) {
Map<String, Field> titleFieldMap = new HashMap<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(ExcelCell.class)) {
continue;
}
// 获取字段代表的单元格标题
ExcelCell excelCell = field.getDeclaredAnnotation(ExcelCell.class);
titleFieldMap.put(excelCell.title(), field);
// 字段格式
if (StringUtils.isNotBlank(excelCell.fieldFormat())) {
fieldFormat.put(field.getName(), excelCell.fieldFormat());
}
}
return titleFieldMap;
}
/**
* 解析标题信息
*
* @param cells 行数据
* @return 标题对应的索引信息
*/
private static Map<String, Integer> getTitle(Row cells) {
Map<String, Integer> titles = new HashMap<>();
for (Cell cell : cells) {
if (cell == null) {
continue;
}
// 列索引
int columnIndex = cell.getColumnIndex();
// 列值
String stringCellValue = cell.getStringCellValue();
titles.put(stringCellValue, columnIndex);
}
return titles;
}
public static void main(String[] args) throws FileNotFoundException {
// File file = new File("/Users/**/模板.xlsx");
// InputStream fileInputStream = new FileInputStream(file);
// 单个sheet解析
// List<Abc> sheetToObject = ExcelUtil.getSheetToObject(fileInputStream, Abc.class);
// log.info("结果1:{}", sheetToObject);
// System.out.println("结果2:" + sheetToObject);
File file = new File("/Users/**/模板.xls");
InputStream fileInputStream = new FileInputStream(file);
// 多个sheet解析
List<List<Abc>> sheetsToObject = ExcelUtil.getSheetListToObject(fileInputStream, Abc.class);
log.info("结果3:{}", sheetsToObject);
System.out.println("结果4:" + sheetsToObject);
}
@Data
public static class Abc {
@ExcelCell(title = "A")
private String a;
@ExcelCell(title = "B")
private String b;
@ExcelCell(title = "C")
private Integer c;
@ExcelCell(title = "D", fieldFormat = "yyyy-MM-dd")
private Date d;
}
}
Excel:
格式:xlsx、xls。(最好xlsx)
最好都设置成文本格式,代码支持 字符串、数值、布尔值、日期 等格式
sheet1:
sheet2
结果:
/Library/Java/JavaVirtualMachines/jdk-11.0.12.jdk/Contents/Home/bin/java ...
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
结果4:[[ExcelUtil.Abc(a=a, b=b, c=c, d=Tue Jan 25 00:00:00 CST 2022), ExcelUtil.Abc(a=1, b=2, c=3, d=Tue Jan 25 00:00:00 CST 2022), ExcelUtil.Abc(a=1, b=2, c=3, d=Tue Jan 25 00:00:00 CST 2022), ExcelUtil.Abc(a=null, b=4, c=null, d=null)], [ExcelUtil.Abc(a=a, b=b, c=c, d=Tue Jan 25 00:00:00 CST 2022), ExcelUtil.Abc(a=1, b=2, c=3, d=Tue Jan 25 00:00:00 CST 2022)]]Process finished with exit code 0