Apache POI二次封装:将List内保存的实体类信息输出到Excel

原创
2018/06/27 16:01
阅读数 1K

在开发一些公司内部使用的工具。有个需求是将大量数据保存成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&lt;String, List&lt;T&gt;&gt;
	 */
	public Map<String, List<T>> getMap() {
		return map;
	}

	/**
	 * 设置封装了Excel信息的map.
	 * @param map Map&lt;String, List&lt;T&gt;&gt;
	 */
	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字符串的分隔符,默认使用英文状态的逗号切割字符串

具体使用方法请自行尝试

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部