1.pom.xml依賴配置
<!-- huTool工具箱 --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.22</version> </dependency> <!-- poi --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.1.0</version> </dependency>
2.代碼
單元格數據模型對象
package com.hdwang.exceltest; import cn.hutool.json.JSONUtil; /** * 單元格數據 */ public class CellData { /** * 行號 */ private int rowIndex; /** * 列號 */ private int cellIndex; /** * 單元格數值 */ private Object value; public int getRowIndex() { return rowIndex; } public void setRowIndex(int rowIndex) { this.rowIndex = rowIndex; } public int getCellIndex() { return cellIndex; } public void setCellIndex(int cellIndex) { this.cellIndex = cellIndex; } public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } @Override public String toString() { return JSONUtil.toJsonStr(this); } }
Bean對象屬性賦值注解
package com.hdwang.exceltest; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 列號,用於給bean對象的屬性賦指定列的值 */ @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.FIELD}) public @interface ColIndex { /** * 列索引號(從0開始),與name二者填一個即可,優先級高於name * * @return */ int index() default -1; /** * 列名稱(從A開始),與index二者填一個即可,優先級低於index * * @return */ String name() default ""; }
表格數據對象
package com.hdwang.exceltest; import cn.hutool.json.JSONUtil; /** * 證券月報 */ public class ZhenquanReport { /** * 名稱 */ @ColIndex(name = "B") private String name; /** * 數值 */ @ColIndex(index = 2) private String value; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } @Override public String toString() { return JSONUtil.toJsonStr(this); } }
讀取方法代碼
package com.hdwang.exceltest; import cn.hutool.poi.excel.ExcelReader; import cn.hutool.poi.excel.ExcelUtil; import cn.hutool.poi.excel.ExcelWriter; import cn.hutool.poi.excel.cell.CellHandler; import org.apache.commons.collections4.CollectionUtils; import org.apache.poi.ss.usermodel.*; import java.io.File; import java.lang.reflect.Field; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; public class Main { public static void main(String[] args) { try { File templateFile = new File("C:\\Users\\hdwang\\Desktop\\test.xlsx"); List<List<CellData>> rowDataList = readExcelData(templateFile, 2, Integer.MAX_VALUE, 1, Integer.MAX_VALUE); System.out.println(rowDataList); List<ZhenquanReport> reports = convertExcelDataToBeanList(rowDataList, ZhenquanReport.class); System.out.println(reports); } catch (Exception ex) { ex.printStackTrace(); } } /** * 讀取表格數據 * * @param templateFile 文件 * @param startRowIndex 起始行號(從0開始) * @param endRowIndex 結束行號(從0開始) * @param startCellIndex 起始列號(從0開始) * @param endCellIndex 結束列號(從0開始) * @return 表格數據 */ private static List<List<CellData>> readExcelData(File templateFile, int startRowIndex, int endRowIndex, int startCellIndex, int endCellIndex) { ExcelReader excelReader = ExcelUtil.getReader(templateFile, 0); List<List<CellData>> rowDataList = new ArrayList<>(); AtomicInteger rowIndex = new AtomicInteger(-1); excelReader.read(startRowIndex, endRowIndex, new CellHandler() { @Override public void handle(Cell cell, Object value) { if (cell == null) { //無單元格跳過 return; } if (cell.getColumnIndex() < startCellIndex || cell.getColumnIndex() > endCellIndex) { //列號不在范圍內跳過 return; } //新行的數據 if (cell.getRowIndex() != rowIndex.get()) { rowDataList.add(new ArrayList<>()); } rowIndex.set(cell.getRowIndex()); //取出新行數據對象存儲單元格數據 List<CellData> cellDataList = rowDataList.get(rowDataList.size() - 1); CellData cellData = new CellData(); cellData.setRowIndex(cell.getRowIndex()); cellData.setCellIndex(cell.getColumnIndex()); cellData.setValue(value); cellDataList.add(cellData); } }); return rowDataList; } /** * 轉換表格數據為bean對象列表 * * @param rowDataList 表格數據 * @param tClass bean類型 * @param <T> * @return bean對象列表 */ private static <T> List<T> convertExcelDataToBeanList(List<List<CellData>> rowDataList, Class<T> tClass) { if (CollectionUtils.isEmpty(rowDataList)) { return new ArrayList<>(); } List<T> beanList = new ArrayList<>(); for (List<CellData> rowData : rowDataList) { try { //實例化bean對象 T bean = tClass.newInstance(); //遍歷字段並賦值 Field[] fields = tClass.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(ColIndex.class)) { ColIndex colIndex = field.getAnnotation(ColIndex.class); int index = colIndex.index(); String name = colIndex.name(); if (index != -1) { //do nothing } else if (!"".equals(name)) { //列名轉索引號(補0為了適應下述方法) index = ExcelUtil.colNameToIndex(name + "0"); } else { throw new RuntimeException("請設置列號(ColIndex注解值必須配置一個)"); } //從行數據中找到指定單元格數據給字段賦值 final int i = index; CellData cellData = rowData.stream().filter(x -> x.getCellIndex() == i).findFirst().orElse(null); if (cellData != null) { Object value = cellData.getValue(); if (field.getType().getName().equals(String.class.getName())) { value = String.valueOf(value); } field.setAccessible(true); field.set(bean, value); } } } beanList.add(bean); } catch (Exception ex) { throw new RuntimeException("實例化對象失敗", ex); } } return beanList; } }
3.表格
4.輸出結果
表格數據對象
[ [ { "cellIndex": 1, "rowIndex": 2, "value": "凈資產" }, { "cellIndex": 2, "rowIndex": 2, "value": 10000 }, { "cellIndex": 3, "rowIndex": 2, "value": " " }, { "cellIndex": 4, "rowIndex": 2, "value": 1 } ], [ { "cellIndex": 1, "rowIndex": 3, "value": "市值" }, { "cellIndex": 2, "rowIndex": 3, "value": 20000 }, { "cellIndex": 4, "rowIndex": 3, "value": 2 } ], [ { "cellIndex": 1, "rowIndex": 4, "value": "標題" } ], [ { "cellIndex": 1, "rowIndex": 5, "value": "凈利潤" }, { "cellIndex": 2, "rowIndex": 5, "value": 1000 }, { "cellIndex": 4, "rowIndex": 5, "value": 3 } ] ]
轉成bean對象列表
[{"name":"凈資產","value":"10000"}, {"name":"市值","value":"20000"}, {"name":"標題"}, {"name":"凈利潤","value":"1000"}]
5.原理總結
hutool poi 工具對POI進行了包裝,實現了很多功能函數。可是在項目實踐中發現還是有所不足。現在自己編寫自定義取行列范圍的數據並實現模型轉換功能,從而方便對表格實現數據讀取與單元格定位操作等。相關實現技術原理如下:
(1) 基於 public void read(int startRowIndex, int endRowIndex, CellHandler cellHandler) 函數實現行過濾,在CellHandler內部實現列過濾,這樣便實現了行列過濾,且取出了行列信息,便於對單元格定位等。
(2) 將CellData轉換為Bean對象的時候,采用反射技術讀取bean對象字段的注解配置的列號信息,從而找到指定的CellData取值並賦值到字段上去。
6.附錄
最新源碼文件:https://github.com/hdwang123/exceltest