分享一个基于 POI 和反射机制实现的通用的读取 Excel 表格数据工具类,读取 Excel 表格数据只需 2 行代码

源代码清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
package org.fanlychie.excel;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.fanlychie.util.DynaBean;
/**
* 可进行读操作的 EXCEL 工具类
*
* @author fanlychie
*/
public class ReadableExcel {
// 工作表
private Sheet sheet;
// 工作表索引
private int sheetIndex;
// Excel 列 与 Bean 属性的顺序映射集
private Map<Integer, String> mapper;
// 动态 Bean
private DynaBean dynaBean;
// 最大解析的单元格索引
private int maxColumnIndex;
/**
* 构造可读的 Excel 实例
*
* @param file
* Excel 文件
*/
public ReadableExcel(File file) {
try {
init(new FileInputStream(file));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
/**
* 构造可读的 Excel 实例
*
* @param in
* Excel 文件输入流
*/
public ReadableExcel(InputStream in) {
init(in);
}
/**
* 解析 Excel 文档到一个 List 列表, 约定第一行为标题行, 标题行不被解析
*
* @param beanClass
* 简单的 JavaBean 类型, Excel 文档的每一行解析成该类的一个实例
* @param mapper
* Excel 文档的列映射到 JavaBean 对象的属性列表<br>
* 格式串 A:["prop_name1", "prop_name2", ...]<br>
* 格式串 B:["1:prop_name1", "2:prop_name2", ...]
* @return 返回解析 Excel 文档的 List 结果列表
*/
public <T> List<T> asList(Class<T> beanClass, String... mapper) {
// 解析映射对照表
this.mapper = parseMapper(mapper);
// 实例化一个动态 Bean 对象
this.dynaBean = new DynaBean(beanClass);
List<T> beanList = new ArrayList<T>();
// 工作表的行集
Iterator<Row> rows = sheet.iterator();
// 约定第一行为标题行, 不解析
Row rowOfTitle = rows.next();
// 实际的总的列数
this.maxColumnIndex = rowOfTitle.getPhysicalNumberOfCells();
// 迭代工作表的行集
while (rows.hasNext()) {
@SuppressWarnings("unchecked")
T bean = (T) parseRow(rows.next());
beanList.add(bean);
}
return beanList;
}
/**
* 设置读取的工作表的索引
*
* @param sheetIndex
* 工作表的索引
*/
public void setSheetIndex(int sheetIndex) {
this.sheetIndex = sheetIndex;
}
/**
* 初始化工作表
*
* @param in
* Excel 输入流
*/
private void init(InputStream in) {
try {
// 工作薄
Workbook workbook = WorkbookFactory.create(in);
// 获取索引指定的工作表
sheet = workbook.getSheetAt(sheetIndex);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
/**
* 解析 Excel 文档的一行到一个 Bean 的对象里
*
* @param row
* Excel 文档中的行对象
* @return 返回解析行的 Bean 对象
*/
private Object parseRow(Row row) {
// 创建一个 Bean 的实例
dynaBean.newBeanInstance();
// 行的单元格列表
Iterator<Cell> cells = row.cellIterator();
// 迭代单元格列表
while (cells.hasNext()) {
// 单元格
Cell cell = cells.next();
// 当前的单元格的索引
int columnIndex = cell.getColumnIndex();
// 索引超出最大解析的索引值结束迭代
if (columnIndex >= maxColumnIndex) {
break;
}
// 单元格映射到 Bean 的属性名称
String name = mapper.get(columnIndex);
// 如果不在对照表内, 跳过该列
if (name == null) {
continue ;
}
// 单元格映射到 Bean 的属性类型
Class<?> type = dynaBean.getFieldType(name);
// 单元格映射到 Bean 的属性的值
Object value;
if (type == Date.class) {
value = cell.getDateCellValue();
} else {
// 设置单元格的类型为字符串类型
cell.setCellType(Cell.CELL_TYPE_STRING);
// 获取单元格字符串内容的值
String stringCellValue = cell.getStringCellValue();
// 进行数据类型转换
value = commonTypeConvert(stringCellValue, type);
}
// 设置到 Bean 对象
dynaBean.setFieldValue(name, value);
}
// 返回 Bean 的实例
return dynaBean.getBean();
}
/**
* 解析映射的字符串
*
* @param mapper
* 格式串 A:["prop_name1", "prop_name2", ...]
* 格式串 B:["1:prop_name1", "2:prop_name2", ...]
* @return 返回解析后的对照表
*/
private Map<Integer, String> parseMapper(String... mapper) {
Map<Integer, String> map = new HashMap<Integer, String>();
String item;
int length = mapper.length;
for (int i = 0; i < length; i++) {
item = mapper[i];
if (item.contains(":")) {
String[] items = item.split(":");
map.put(Integer.valueOf(items[0]), items[1]);
} else {
map.put(i, item);
}
}
return map;
}
/**
* 常用的数据类型转换
*
* @param value
* 被转换的对象
* @param type
* 期望得到的类型
* @return 返回转换后的数据类型对象
*/
private Object commonTypeConvert(String value, Class<?> type) {
if (type == String.class) {
return value;
}
if (type == Character.TYPE || type == Character.class) {
return value.charAt(0);
}
if (type == Boolean.TYPE || type == Boolean.class) {
// 真值
if (value == "1" || value.equals("是")
|| value.equalsIgnoreCase("Y")
|| value.equalsIgnoreCase("YES")
|| value.equalsIgnoreCase("T")
|| value.equalsIgnoreCase("TRUE")) {
return true;
}
// 假值
if (value == "0" || value.equals("否")
|| value.equalsIgnoreCase("N")
|| value.equalsIgnoreCase("NO")
|| value.equalsIgnoreCase("F")
|| value.equalsIgnoreCase("FALSE")) {
return false;
}
}
// 解析成双精度类型
Double objVal = Double.valueOf(value);
// 以下均基于双精度数据上作的类型转换
if (type == Byte.TYPE || type == Byte.class) {
return objVal.byteValue();
}
if (type == Short.TYPE || type == Short.class) {
return objVal.shortValue();
}
if (type == Integer.TYPE || type == Integer.class) {
return objVal.intValue();
}
if (type == Long.TYPE || type == Long.class) {
return objVal.longValue();
}
if (type == Float.TYPE || type == Float.class) {
return objVal.floatValue();
}
if (type == Double.TYPE || type == Double.class) {
return objVal;
}
throw new ClassCastException("Cannot cast java.lang.String to " + type.getName());
}
}

POI 依赖

1
2
3
4
5
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.9</version>
</dependency>

测试对象清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public static class Person {
private int id;
private String name;
private String sex;
private boolean local;
private Date birthday;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isLocal() {
return local;
}
public void setLocal(boolean local) {
this.local = local;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getBirthdayStr() {
if (birthday == null) {
return null;
}
return new SimpleDateFormat("yyyy-MM-dd").format(birthday);
}
@Override
public String toString() {
return id + "\t" + name + "\t" + sex + "\t" + local + "\t" + getBirthdayStr();
}
}

测试 Excel 表格数据清单

测试清单1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
File excelFile = new File("src/test/resources/person_excel.xlsx");
// 创建一个可读的 Excel 对象
ReadableExcel excel = new ReadableExcel(excelFile);
// 解析 Excel 数据为 JavaBean 对象
List<Person> persons = excel.asList(Person.class, "id", "name", "sex", "local", "birthday");
for (Person person : persons) {
System.out.println(person);
}
}

解析语法1:["id", "name", "sex", "local", "birthday"] 从左至右依次对应 Excel 表格的列数据

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1 刘一 男 false 1990-04-02
2 陈二 男 false 1991-08-01
3 张三 女 false 1990-04-01
4 李四 女 false 1992-05-04
5 王五 女 true 1992-05-02
6 赵六 男 true 1991-07-06
7 孙七 女 true 1990-09-03
8 周八 女 false 1990-03-05
9 吴九 男 true 1993-01-03

测试清单2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
File excelFile = new File("src/test/resources/person_excel.xlsx");
// 创建一个可读的 Excel 对象
ReadableExcel excel = new ReadableExcel(excelFile);
// 解析 Excel 数据为 JavaBean 对象
List<Person> persons = excel.asList(Person.class, "0:id", "2:name", "1:sex");
for (Person person : persons) {
System.out.println(person);
}
}

解析语法2:["0:id", "2:name", "1:sex"] 只提取想要的列的数据(0 代表第 1 列)

"2:name" 表示将 Excel 表格第 3 列的数据解析成 JavaBean 对象的 name 属性的值

["0:id", "2:name", "1:sex"] 等效于 ["id", "2:name", "1:sex"],其余类似,可灵活提取表格数据

场景:

我们公司使用的短信供应商目前已有 3 家,在统计数据的时候,需要先在供应商短信系统导出短信

消息的 Excel 文档然后统计相关信息,但是不同供应商导出的 Excel 模板文件格式不一致,因此衍生

了这种解析 Excel 表格数据的处理方法。

假设供应商 A 导出的手机号码在第 2 列,供应商 B 导出的手机号码在第 3 列,我们可以这样做:

供应商 A 使用 "1:mobile"

供应商 B 使用 "2:mobile"

这样一来,我们只需要根据不同的供应商传整数值定向解析列的数据即可,这样可以适配多个不同

的 Excel 模板,只要 2 行代码完成解析工作

注:非公开公司代码,纯为个人积累及思考编码实现

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1 男 刘一 false null
2 男 陈二 false null
3 女 张三 false null
4 女 李四 false null
5 女 王五 false null
6 男 赵六 false null
7 女 孙七 false null
8 女 周八 false null
9 男 吴九 false null

["0:id", "2:name", "1:sex"] 这里故意将 name 和 sex 倒了过来,将第 2 列解析成 sex,第 3 列解析成 name,以示区分