在开发一些公司内部使用的工具。有个需求是将大量数据保存成Excel文件。于是封装了一下POI,比较傻瓜式,用起来方便,但功能也不强。后面会有演示。
加上注释一共才300多行,所以直接贴源代码了。
依赖关系都写在注释里面了。
package tools;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
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.xssf.usermodel.XSSFWorkbook;
/**
* 写出实体类信息到Excel文件.</br>
* 依赖:poi-3.16.jar、poi-ooxml-3.16.jar、poi-ooxml-schemas-3.16.jar、
* xmlbeans-2.6.0.jar、commons-collections4-4.1.jar
* @author AL
*
*/
public class ExcelBuilder<T> {
/**
* 特殊列.<br/>
* 特殊列:即使设置了在需要时自动转换单元格格式为数字格式,也不会被转换格式的列。
* 默认为null。
* 注意:列的下标从0开始。
*/
private int[] spColumns;
/**
* 分隔符.
* 默认为英文输入状态下的逗号。
* @see ExcelBuilder#setSeparativeSign(String)
*/
private String separativeSign = ",";
/**
* 标记是否自动转换单元格格式为数字格式(若待填充的文本为数字).
* 默认为true。
* @see ExcelBuilder#setNumFormat(boolean)
*/
private boolean transform = true;
/**
* 转换单元格格式为数字格式时,数字保留到小数点后的精度.
* 默认为2。
* @see ExcelBuilder#setScale(int)
*/
private int scale = 2;
/**
* 匹配数字(非科学计数法).
*/
private final Pattern general_num=Pattern.compile("^-{0,1}[0-9]+\\.{0,1}[0-9]*$");
/**
* 匹配数字(科学计数法).
*/
private final Pattern notation_num=Pattern.compile("^-{0,1}[0-9]+\\.{0,1}[0-9]*[Ee]{1}-{0,1}[0-9]+$");
/**
* 等号(英文输入状态).
*/
private final String equal_sign ="=";
/**
* 字符串“0”.
*/
private final String zero = "0";
/**
* 字符串“空”.
*/
private final String blank = "空";
/**
* 封装了Excel信息的Map.
*/
private Map<String,List<T>> map;
/**
* 输出的文件名,包含路径(末尾不需要“.xlsx”后缀,且构造方法不会检查路径是否合法,请保证该路径存在并可用).
*/
private String fileName;
public ExcelBuilder() {
super();
}
/**
* @param map key为Excel中每个分页的名称、value为分页的内容
* @param fileName 输出的文件名,包含路径(末尾不需要“.xlsx”后缀,且构造方法不会检查路径是否合法,请保证该路径存在并可用)
*/
public ExcelBuilder(Map<String, List<T>> map, String fileName) {
this.map = map;
this.fileName = fileName;
}
/**
* 获取特殊列.<br/>
* 特殊列:即使设置了在需要时自动转换单元格格式为数字格式,也不会被转换格式的列。
* 注意:列的下标从0开始。
* @return
*/
public int[] getSpColumns() {
return spColumns;
}
/**
* 设置特殊列.<br/>
* 特殊列:即使设置了在需要时自动转换单元格格式为数字格式,也不会被转换格式的列。
* 若不设置,默认为null。
* 注意:列的下标从0开始。
* @param spColumns int类型的变长参数
*/
public void setSpColumns(int... spColumns) {
this.spColumns = spColumns;
}
/**
* 设置分隔符.
* 若不设置此项,则默认为英文输入状态下的逗号。
* @param sign
*/
public void setSeparativeSign(String separativeSign) {
if (separativeSign != null && separativeSign.isEmpty() == false)
this.separativeSign = separativeSign;
}
/**
* 获取分隔符.
* @return separativeSign
*/
public String getSeparativeSign() {
return this.separativeSign;
}
/**
* 设置是否自动转换单元格格式为数字格式(若待填充的文本为数字).
* 若不设置此项,则默认自动转换。
*/
public void setTransform(boolean transform) {
this.transform = transform;
}
/**
* 获取是否自动转换单元格格式为数字格式的标记.
* @return transform
*/
public boolean isTransform() {
return this.transform;
}
/**
* 设置转换单元格格式为数字格式时,数字保留到小数点后的精度.
* 若不设置此项,则默认为2。
* @param i
*/
public void setScale(int scale) {
if(scale>-1)
this.scale = scale;
}
/**
* 获取转换单元格格式为数字格式时,数字保留到小数点后的精度.
* @return scale
*/
public int getScale() {
return this.scale;
}
/**
* 获取封装了的Excel信息.
* @return Map<String, List<T>>
*/
public Map<String, List<T>> getMap() {
return map;
}
/**
* 设置封装了Excel信息的map.
* @param map Map<String, List<T>>
*/
public void setMap(Map<String, List<T>> map) {
this.map = map;
}
/**
* 获取文件名(包含路径).
* @return fileName
*/
public String getFileName() {
return fileName;
}
/**
* 设置输出的文件名,包含路径(末尾不需要“.xlsx”后缀,且构造方法不会检查路径是否合法,请保证该路径存在并可用).
* @param fileName
*/
public void setFileName(String fileName) {
this.fileName = fileName;
}
/**
* 写出内容到Excel.
* @throws Exception
*/
public void write() throws Exception {
// 新建文件输出流及Excel表格缓存区
FileOutputStream out=null;
Workbook excel = null;
try {
out=new FileOutputStream(fileName+".xlsx");
excel = new XSSFWorkbook();
StringBuilder sb = new StringBuilder(zero);
for(int i=0;i<scale;i++) {
if(i==0) {
sb.append(".0");
} else {
sb.append(zero);
}
}
// 此对象用于设置单元格格式为数字格式并保留小数点后的位数,备用
CellStyle cellStyle = excel.createCellStyle();
cellStyle.setDataFormat(excel.createDataFormat().getFormat(sb.toString()));
// 若map为空,则重构一个map,装入一个空白键值对
if(map==null || map.isEmpty()){
map=new HashMap<>();
map.put(blank, null);
}
// 遍历键值对,将表格名称和表格内容传递给下一层,用于写出
for(Entry<String, List<T>> entry : map.entrySet()){
// 向Excel(缓存区)内填充内容
writeWorkbook(entry.getValue(),entry.getKey(),excel,cellStyle);
}
// 写出到磁盘文件
excel.write(out);
} catch (IOException e) {
throw new Exception("输出Excel失败",e);
} catch(StringIndexOutOfBoundsException e){
throw new Exception("分隔符不正确导致无法按“=”分割出表头",e);
} catch (Exception e) {
throw new Exception("写出内容到Excel遭遇未知异常",e);
} finally {
if (out!=null)
out.close();
if (excel!=null)
excel.close();
}
}
/**
* 向Excel(缓存区)内填充内容.
* @param result
* @param sheetName
* @param excel
* @param cellStyle
*/
private void writeWorkbook(List<T> result, String sheetName, Workbook excel, CellStyle cellStyle)
throws StringIndexOutOfBoundsException, Exception {
// 若sheetName为空,则填充“空”
if(sheetName==null || sheetName.isEmpty()){
sheetName=blank;
}
Sheet sheet = excel.createSheet(sheetName);
// 若集合为空,则写出“空”字符
if (result == null || result.isEmpty()) {
sheet.createRow(0).createCell(0).setCellValue(blank);
return;
}
// 按分隔符将集合内元素toString的字符串切割成数组
String[] temp = result.get(0).toString().split(separativeSign);
Row row = sheet.createRow(0);// 获取此sheet表格的第一行
int times=temp.length;
for (int t = 0; t < times; t++) {// 遍历数组,用于构造表头
row.createCell(t).setCellValue(temp[t].substring(0, temp[t].indexOf(equal_sign)));// 只取“=”之前的字符
}
// 遍历集合,填充表格内容
int size=result.size();
for (int i = 1; i <= size; i++) {
row = sheet.createRow(i);
String[] infos=result.get(i - 1).toString().split(separativeSign);// 将toString返回的字符串按分隔符切割成数组
int round=infos.length;
for (int j = 0; j < round; j++) {
// 只取“=”之后的字符
String info =infos[j].substring(infos[j].indexOf(equal_sign) + 1, infos[j].length()).trim();
// 获取单元格
Cell cell=row.createCell(j);
// 判断是否需要转换单元格格式
if (transform && transform(j)
&& (general_num.matcher(info).matches() || notation_num.matcher(info).matches())) {
cell.setCellStyle(cellStyle);
cell.setCellValue(Double.parseDouble(info));
} else {
cell.setCellValue(info);
}
}
}
}
/**
* 按下标判断所在列是否属于不需要转换格式的特殊列.
* @param index
* @return
*/
private boolean transform(int index) {
if (spColumns != null) {
for (int i : spColumns) {
if (index == i) {
return false;
}
}
}
return true;
}
}
没有定义异常,直接使用了Exception,建议使用时自定义一个异常类。
下面是使用方法演示:
1、重写toString方法,输出结果为以下样式(符号全部是英文符号):
ExcelBuilder需要通过toString方法来获得实体类内封装的信息。不使用反射API的原因是为了减少代码量(懒)。其实只需要把eclipse自动生成的toString稍微修改一下就行了。
2、用List封装实体类,用Map封装List,Map的每一个Key就是最终生成的Excel的每一个分页(sheet),使用Map构造一个ExcelBuilder实体类:
构造器的第二个参数是输出的Excel文件的路径(包含文件名,不需要后缀)。
最终输出结果如下
默认会将填充了数字的单元格转换为数字类型,并且保留到小数点后两位。
3、如果不希望自动转换单元格格式,可使用setTransform方法,传参数为false。
输出结果如下
所有单元格都默认为文本格式,不会转换为数字。
4、如果希望有的单元格自动转换为数字,有的单元格不转换,可使用setSpColumns方法来设置特殊列
* 特殊列:即使设置了在需要时自动转换单元格格式为数字格式,也不会被转换格式的列。
* 此方法的参数是一个变长参数集,可一次传多个参数。
* 注意:列的下标从0开始。
效果如下
5、其它API:
setScale方法可设置数字格式的单元格的数字精度,默认为精确到小数点后两位
setSeparativeSign方法可设置用于切割toString字符串的分隔符,默认使用英文状态的逗号切割字符串
具体使用方法请自行尝试