炎炎夏日最新版Excel导入导出工具类火热出炉

本文介绍了一个改进的Excel工具类,支持Java8日期类型、自定义表头和内容样式等功能,适用于单个类属性的Excel文件转换。

辛苦写的,转载请注明来源^_^

一、为什么要写这个Excel工具类

上个项目有个功能点需要导出信息到Excel文件,于是到网上找了工具类xdemo,首先感谢原作者的奉献,使用很简单。但在使用的过程中也发现了几个问题(可能是我用的版本太老导致的):

1.类的属性为空时,没有相应处理,Excel中的单元格会前移一个单元格。
2.不支持Java8的LocalDateTime的日期转换。
3.Excel反序列化List时,不支持自定义日期格式。
4.由于写的时间较早,其中很多POI的API都是标记过时的。
5.代码中大量使用for循环,if判断,对于代码洁癖的我,有点不习惯(对原作者没有任何恶意^_^,不要误解)

二、和老版的Excel工具类对比

功能点新版ExcelUtils旧版ExcelUtils
自定义表头样式支持不支持
自定义内容样式支持不支持
自定义序列化属性转换支持支持
自定义反序列属性转换支持支持
支持LocalDateTime支持不支持
自定义表格合并不支持(写出来太费劲^_^)支持

三、使用的依赖

Gradle

//FastJson
compile ("com.alibaba:fastjson:1.2.35")
//POI
compile ("org.apache.poi:poi:3.16")
compile ("org.apache.poi:poi-ooxml:3.16")

Maven

//FastJson
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.35</version>
</dependency>
//POI
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.16</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.16</version>
</dependency>

四、使用的技术

1.使用FastJson作为Json转换工具(号称最快的Json解析工具)
2.大量使用Java8的Stream进行链式操作(好像也写了一些for循环……)
3.利用Java8的lambda表达式简化语法
4.利用Java8的Optional排除空指针异常

工具中采用的思想是约定大于配置。必需按照约定好的步骤进行调用。

五、List转Excel的实现思想

1.顺序获取类中所有属性的Excel注解,拿到Excel注解的name属性,设置为表头。
2.逐个顺序获取对象中带有Excel注解的属性值,然后做对应的转换(false->假,true->真等),之后写入到cell中,cell的类型均为String类型。
3.写出文件,搞定了

六、Excel转List的实现思想

可不可以把每行转换成一个对象的Json格式,再用Json工具转换成对象呢?这样效率可能会慢一点,但事实证明并没有慢多少,而且很方便》》》》

1.读取类中的所有带有Excel注解的属性的属性名(个数应和Exel的列的个数对应)。
2.将每一行组成Map<String,String>的数据,代表一个对象。那么多选就是List<Map<String,String>>,然后将其序列化,就类似于这样的Json:

[
{
    "name":"king1",
    "age":"18"
},
{
    "name":"king2",
    "age":"19"
},
......
]

组合成了对象数组的Json格式了,这时候只要调用Json工具的List list = JSON.parseArray(json, Class clazz)方法就可以拿到反序列的对象了,这个做法是不是有点骚气==》==》》》》》》

约定:

List中的类中带有Excel注解的属性必需与Excel文件中的数据完全对应,才能反序列成功。说的简单点,就是反序列时类属性上的@Excel注解要和导出为Excel时加的@Excel注解一样。

七、Excel工具类代码

7.1 Excel.java
package com.kingboy.common.utils.excel;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author kingboy--KingBoyWorld@163.com
 * @date 2017/7/26 下午1:23
 * @desc Excel注解,用以生成Excel表格文件.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Excel {

    //列名
    String name() default "";

    //宽度
    int width() default 25;

    //忽略该字段
    boolean skip() default false;

    //日期格式
    String dateFormat() default "yyyy年MM月dd日 HH:mm";

}
7.2 ExcelStyle.java
package com.kingboy.common.utils.excel;

import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;

/**
 * @author kingboy--KingBoyWorld@163.com
 * @date 2017/7/26 下午4:58
 * @desc 设置Excel的样式.使用了build模式,链式赋值
 */
public class ExcelStyle {

    public ExcelStyle() { }

    //对齐方式
    private HorizontalAlignment align;

    //字体  "黑体"等
    private String fontName;

    //是否加粗,对表头不生效
    private boolean isBold;

    //字体大小
    private short size;

    //字体颜色
    private short fontColor;

    //边框粗细
    private BorderStyle borderStyle;

    //背景颜色
    private short backColor;



    public ExcelStyle setAlign(HorizontalAlignment align) {
        this.align = align;
        return this;
    }

    public ExcelStyle setFontName(String fontName) {
        this.fontName = fontName;
        return this;
    }

    public ExcelStyle setBold(boolean bold) {
        isBold = bold;
        return this;
    }

    public ExcelStyle setSize(short size) {
        this.size = size;
        return this;
    }

    public ExcelStyle setFontColor(short fontColor) {
        this.fontColor = fontColor;
        return this;
    }

    public ExcelStyle setBorderStyle(BorderStyle borderStyle) {
        this.borderStyle = borderStyle;
        return this;
    }

    public ExcelStyle setBackColor(short backColor) {
        this.backColor = backColor;
        return this;
    }

    public HorizontalAlignment getAlign() {
        return align;
    }

    public String getFontName() {
        return fontName;
    }

    public boolean isBold() {
        return isBold;
    }

    public short getSize() {
        return size;
    }

    public short getFontColor() {
        return fontColor;
    }

    public BorderStyle getBorderStyle() {
        return borderStyle;
    }

    public short getBackColor() {
        return backColor;
    }
}
7.3 ExcelUtils.java
package com.kingboy.common.utils.excel;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author kingboy--KingBoyWorld@163.com
 * @date 2017/7/26 下午1:25
 * @desc Excel工具类. 只针对单个类内的属性进行转换。
 * 额外说明:本工具类支持设置数据行的格式,但由于这个功能占用执行时间过多(大约十倍),所以在205行注释掉了,有需要可以打开
 * 使用说明-博客:http://blog.youkuaiyun.com/kingboyworld/article/details/76253785
 */
public final class ExcelUtils {

    private ExcelUtils() { }

    /**
     * 将List转换成Excel
     * @param list 数据集合
     * @param filePath java7中的文件操作  创建方式:Paths.get("文件地址");
     * @param <T> bean类型
     * @return
     * @throws Exception
     */
    public static <T> void listToExcel(List<T> list, Path filePath, Map<String, Map<String, String>> fieldMapper,
                                          ExcelStyle headStyle, ExcelStyle contentStyle) throws Exception {
        //List为null或者empty抛出异常
        Optional.ofNullable(list)
                .filter(l -> !l.isEmpty())
                .orElseThrow(NullPointerException::new);

        //1. 获取类的字符串属性
        List<Excel> excelList = getExcelAnnoList(list.get(0).getClass());
        //2. 创建工件薄,工件表
        Workbook workbook = new SXSSFWorkbook();
        Sheet sheet = workbook.createSheet();
        //3. 设置表头
        setHead(excelList, sheet, headStyle);
        //4. 设置内容
        setContent(list, sheet, fieldMapper, contentStyle);
        //5. 写文件
        workbook.write(Files.newOutputStream(filePath));
    }

    //每次转换多少行
    private static Integer size = 1_000;

    /**
     * excel转换为List,基本思路是拼接成Json,然后用Json工具转换为List
     * 必需保证类里面的带有Excel注解属性的顺序和Excel文件中标题的顺序相对应
     * @param filePath 文件路径    创建方式:Paths.get("文件地址");
     * @param clazz 类
     * @param <T>
     * @return
     */
    public static<T> List<T> excelToList(Path filePath, Class<T> clazz, Map<String, Map<String, String>> fieldMapper)
            throws Exception {
        Workbook workbook = new XSSFWorkbook(Files.newInputStream(filePath));
        Sheet sheet = workbook.getSheetAt(0);

        //1. 获取类和Excel表格对应的属性,有序放入ArrayList中
        ArrayList<String> fieldList = getFieldNameByExcelAnno(clazz);
        //2. 获取结果
        ArrayList<T> resultList = getClazzArrayList(sheet, clazz, fieldList, fieldMapper);
        return resultList;
    }



    /*------------------------------ExcelToList的调用方法------------------------------------*/

    /**
     *
     * @param sheet 工作表
     * @param fieldList 获取类中有映射关系的属性名
     * @param <T>
     * @return
     */
    private static <T> ArrayList<T> getClazzArrayList(Sheet sheet, Class<T> clazz, ArrayList<String> fieldList,
                                                      Map<String, Map<String, String>> fieldMapper) {
        //数据行数
        int rowNumber = sheet.getPhysicalNumberOfRows() - 1;
        //读取次数
        int times = rowNumber / size + 1;
        //结果集
        ArrayList<T> result = new ArrayList<T>(rowNumber);
        Stream.iterate(1, i -> i + 1)
                .limit(times - 1)
                .forEach(i -> {
                        result.addAll(getArrayListFromMap(
                              readExcel((i - 1) * size + 1, i == times ? rowNumber : size, sheet, fieldList),
                              clazz,
                              fieldMapper)
                        );
                });
        return result;
    }

    /**
     * 解析Json字符串
     * @param mapList Excel表中的数据
     * @param clazz
     * @param fieldMapper 属性转换容器
     * @param <T>
     * @return
     */
    private static <T> List<T> getArrayListFromMap(List<Map<String, String>> mapList, Class<T> clazz,
                                                   Map<String, Map<String, String>> fieldMapper) {
        //记录默认的日期格式
        String tempDateFormat = JSONObject.DEFFAULT_DATE_FORMAT;
        //设置日期编码
        getFieldWithExcel(clazz)
                .filter(field -> field.getType() == Date.class || field.getType() == LocalDateTime.class)
                .findFirst()
                .ifPresent(field ->
                        JSONObject.DEFFAULT_DATE_FORMAT = field.getAnnotation(Excel.class).dateFormat());

        //转换属性
        if (fieldMapper != null) {
            mapList.stream().forEach(map -> {
                map.forEach((key, value) -> {
                    if (fieldMapper.get(key) != null) {
                        //真实的属性值
                        String realValue = fieldMapper.get(key).get(value);
                        //如果有对应的值,就设置对应的值
                        map.put(key, realValue == null ? value : realValue);
                    }
                });
            });
        }

        List<T> list = JSON.parseArray(JSON.toJSONString(mapList), clazz);
        //还原默认的日期格式
        JSONObject.DEFFAULT_DATE_FORMAT = tempDateFormat;

        return list;
    }

    /**
     * 读取指定数量的数据,每行都拼接成一个Map<String, String>的集合
     * @param start 开始行
     * @param size 长度
     * @param sheet
     * @param fieldList 获取类中有映射关系的属性名
     * @return
     */
    private static <T> ArrayList<Map<String, String>> readExcel(int start, int size, Sheet sheet, ArrayList<String> fieldList) {
        ArrayList<Map<String, String>> listMap = new ArrayList<>();
        for (int i = start; i < start + size; i++) {
            Map<String, String> map = new TreeMap<>();
            for (int j = 0; j < fieldList.size(); j++) {
                map.put(fieldList.get(j), sheet.getRow(i).getCell(j).getStringCellValue());
            }
            listMap.add(map);
        }
        return listMap;
    }

    /**
     * 获取属性名
     * @param clazz
     * @param <T>
     * @return
     * @throws Exception
     */
    private static <T> ArrayList<String> getFieldNameByExcelAnno(Class<T> clazz) throws Exception {
        return getFieldWithExcel(clazz)
                .map(field -> field.getName())
                .collect(Collectors.toCollection(ArrayList::new));
    }

    /*------------------------------ListToExcel的调用方法------------------------------------*/

    /**
     * 写入内容
     * @param list 数据内容
     * @param sheet
     * @param fieldMapper 属性转换
     * @param <T>
     */
    private static <T> void setContent(List<T> list, Sheet sheet, Map<String, Map<String, String>> fieldMapper, ExcelStyle contentStyle) {
        Stream.iterate(0, item -> item + 1).limit(list.size())
                .forEach(item -> {
                    //当前行
                    Row row = sheet.createRow(item + 1);
                    //每个对象的属性值
                    List<String> fieldValue = getFiledValueIfIsExcel(list.get(item), fieldMapper);
                    Stream.iterate(0, i -> i + 1)
                            .limit(fieldValue.size())
                            .forEach(i -> {
                                Cell cell = row.createCell(i);
                                cell.setCellType(CellType.STRING);
                                //cell.setCellStyle(getContentStyle(sheet.getWorkbook(), contentStyle));
                                cell.setCellValue(fieldValue.get(i));
                            });
                });
    }

    /**
     * 写入标题
     * @param excelList 标题
     * @param sheet
     */
    private static void setHead(List<Excel> excelList, Sheet sheet, ExcelStyle headStyle) {
        Row row = sheet.createRow(0);
        Stream.iterate(0, item -> item + 1)
                .limit(excelList.size())
                .forEach(item -> {
                    Cell cell = row.createCell(item);
                    cell.setCellType(CellType.STRING);
                    cell.setCellValue(excelList.get(item).name());
                    cell.setCellStyle(getTitleStyle(sheet.getWorkbook(), headStyle));
                    sheet.setColumnWidth(item, excelList.get(item).width() * 2 << 6);
                });
    }

    /**
     * 设置头样式
     * @param workbook
     * @return
     */
    private static CellStyle getTitleStyle(Workbook workbook, ExcelStyle headStyle) {
        CellStyle cellStyle = workbook.createCellStyle();
        headStyle = headStyle == null ? new ExcelStyle() : headStyle;

        //对齐方式
        cellStyle.setAlignment(headStyle.getAlign() == null ? HorizontalAlignment.CENTER : headStyle.getAlign());

        //设置字体
        Font font = workbook.createFont();
        String fontName = headStyle.getFontName();
        font.setFontName(null == fontName ? "黑体" : fontName);
        //字体大小
        font.setFontHeightInPoints(headStyle.getSize() <= 0 ? 14 : headStyle.getSize());
        font.setBold(true);
        font.setColor(headStyle.getFontColor() <= 0 ? HSSFColor.BLACK.index : headStyle.getFontColor());
        cellStyle.setFont(font);

        //背景
        cellStyle.setFillForegroundColor(headStyle.getBackColor() <= 0
                ? HSSFColor.SEA_GREEN.index
                : headStyle.getBackColor());
        cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);

        BorderStyle border = headStyle.getBorderStyle() == null ? BorderStyle.MEDIUM : headStyle.getBorderStyle();
        //边框
        cellStyle.setBorderLeft(border);
        cellStyle.setBorderTop(border);
        cellStyle.setBorderRight(border);
        cellStyle.setBorderBottom(border);

        //自动换行
        //cellStyle.setWrapText(true);
        return cellStyle;
    }

    /**
     * 设置内容样式,占用过大,并没有开启这个功能
     * @param workbook
     * @return
     */
    private static CellStyle getContentStyle(Workbook workbook, ExcelStyle contentStyle) {
        contentStyle = contentStyle == null ? new ExcelStyle() : contentStyle;
        CellStyle cellStyle = workbook.createCellStyle();

        //对齐方式
        cellStyle.setAlignment(contentStyle.getAlign() == null ? HorizontalAlignment.LEFT : contentStyle.getAlign());

        //设置字体
        Font font = workbook.createFont();
        String fontName = contentStyle.getFontName();
        font.setFontName(null == fontName ? "黑体" : fontName);
        //字体大小
        font.setFontHeightInPoints(contentStyle.getSize() <= 0 ? 12 : contentStyle.getSize());
        font.setBold(contentStyle.isBold());
        font.setColor(contentStyle.getFontColor() <= 0 ? HSSFColor.BLACK.index : contentStyle.getFontColor());
        cellStyle.setFont(font);

        //背景
        cellStyle.setFillForegroundColor(contentStyle.getBackColor() <= 0
                ? HSSFColor.WHITE.index
                : contentStyle.getBackColor());
        cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);

        BorderStyle border = contentStyle.getBorderStyle() == null ? BorderStyle.THIN : contentStyle.getBorderStyle();
        //边框
        cellStyle.setBorderLeft(border);
        cellStyle.setBorderTop(border);
        cellStyle.setBorderRight(border);
        cellStyle.setBorderBottom(border);

        //自动换行
        //cellStyle.setWrapText(true);
        return cellStyle;
    }

    /**
     * 拿到一个类中属性上的Excel注解
     * @param clazz
     * @param <T>
     * @return
     * @throws Exception
     */
    private static <T> List<Excel> getExcelAnnoList(Class<T> clazz) throws Exception {
        return  getFieldWithExcel(clazz)
                .map(field -> field.getAnnotation(Excel.class))
                .collect(Collectors.toCollection(ArrayList::new));
    }

    /**
     * 获取带有Excel注解的属性
     * @param clazz
     * @param <T>
     * @return
     */
    private static <T> Stream<Field> getFieldWithExcel(Class<T> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        return Arrays.stream(fields)
                .filter(field -> {
                    Excel excel = field.getAnnotation(Excel.class);
                    return excel != null && !excel.skip();
                });
    }

    /**
     * 获取一个类包含Excel注解的属性的值
     * @param t
     * @param <T>
     * @return
     */
    private static  <T> List<String> getFiledValueIfIsExcel(T t, Map<String, Map<String, String>> fieldMapper) {
        return getFieldWithExcel(t.getClass()).map(field -> {
            field.setAccessible(true);
            Object o = null;

            try {
                 o = field.get(t);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            //如果属性值为空,返回空字符串
            if (Objects.isNull(o)) {
                o =  "";
            }
            //如果是Date类型
            if (o instanceof Date) {
                o =  new SimpleDateFormat(field.getAnnotation(Excel.class).dateFormat()).format((Date) o);
            }
            //如果是LocalDateTime类型,支持Java8LocalDateTime
            if (o instanceof LocalDateTime) {
                o =  ((LocalDateTime) o).format(DateTimeFormatter.ofPattern(field.getAnnotation(Excel.class).dateFormat()));
            }
            //bool和int属性转换,感觉最常用这两个,其它的需要再扩充
            if (fieldMapper != null && (o instanceof Boolean || o instanceof Integer)) {
                Map<String, String> map = fieldMapper.get(field.getName());
                o =  map == null ? o : map.get(o.toString()) == null ? o : map.get(o.toString());
            }
            //其它的返回toString
            return o.toString();

        }).collect(Collectors.toCollection(ArrayList::new));
    }

}

八、使用方法

//把List转换为Excel
public static <T> void listToExcel(List<T> list, Path filePath, Map<String, Map<String, String>> fieldMapper, ExcelStyle headStyle, ExcelStyle contentStyle) {}

参数说明:
1. list: 要转换的类(类中需要转换的属性需要加上Excel注解)
2. filePath: Java7中的文件操作类。Excel导出的地址
3. fieldMapper: 属性转换(可以为null)
4. headStyle: 头部样式(可以为null),为空时使用默认样式。
5. contentStyle:内容体的样式(可以为null),这个默认不生效(由于这个功能占用执行时间过多(大约十倍),所以在205行注释掉了,有需要可以打开)

//把Excel转换为List

excelToList(Path filePath, Class<T> clazz, Map<String, Map<String, String>> fieldMapper){}

参数说明:
1. filePath: Java7中的文件操作类。Excel导出的地址
2. clazz: 要转换的类
3. fieldMapper: 属性转换(可以为null)
4. 如果有日期等,需要在类属性的Excel注解上给dateFormat属性赋值。

九、测试用例

//基础类
public class ExcelDTO {

    @Excel(name = "姓名", width = 30)
    private String name;

    @Excel(name = "年龄", width = 30)
    private Integer age;

    @Excel(skip = true)
    private String password;

    @Excel(name = "生日", dateFormat = "yyyy年MM/dd HH:mm", width = 50)
    private Date birth;

    @Excel(name = "帐户")
    private Double money;

    @Excel(name = "冻结")
    private Boolean lock;

//getter,setter

}

测试


/**
 * @author kingboy--KingBoyWorld@163.com
 * @date 2017/7/26 下午2:04
 * @desc 测试我写的Excel工具类.
 */
public class MyExcelTest {


    /**
     * 导出为Excel,将属性进行转换。
     * @throws Exception
     */
    @Test
    public void ListToExcel_test() throws Exception {
        long start = System.nanoTime();

        //属性转换
        Map<String, Map<String, String>> map = new HashMap<>();
        //age的转换
        Map<String, String> value = new HashMap<>();
        value.put("12", "12岁");
        value.put("15", "15岁");
        value.put("18", "18岁");
        map.put("age", value);
        //lock的转换
        Map<String, String> lock = new HashMap<>();
        lock.put("true", "锁");
        lock.put("false", "正常");
        map.put("lock", lock);

        ExcelUtils.listToExcel(getExcelDTOList(), Paths.get("/Users/kingboy/Desktop/My.xlsx"), map, null, null);

        System.out.println("-----my-------" + (System.nanoTime() - start) / 1000_000 + "毫秒");
    }

    /**
     * 导出为Excel,没有将属性进行转换。
     * @throws Exception
     */
    @Test
    public void ListToExcelNoMapper_test() throws Exception {
        //没有转换属性测试
        ExcelUtils.listToExcel(getExcelDTOList(), Paths.get("/Users/kingboy/Desktop/My1.xlsx"), null, new ExcelStyle().setBackColor(HSSFColor.WHITE.index),new ExcelStyle().setAlign(HorizontalAlignment.CENTER));

    }

    /**
     * 导入为List,将属性进行转换。
     * 注意属性转换是反过来的。
     * @throws Exception
     */
    @Test
    public void ExcelToList_test() throws Exception {
        long start = System.nanoTime();

        Map<String, Map<String, String>> map = new HashMap<>();
        Map<String, String> value = new HashMap<>();
        value.put("小", "12");
        value.put("中", "15");
        value.put("大", "18");
        Map<String, String> lock = new HashMap<>();
        lock.put("锁", "true");
        lock.put("正常", "false");
        map.put("age", value);
        map.put("lock", lock);
        List<ExcelDTO> excelDTOS = ExcelUtils.excelToList(Paths.get("/Users/kingboy/Desktop/My.xlsx"), ExcelDTO.class, map);

        System.out.println("----my--------" + (System.nanoTime() - start) / 1000_000 + "毫秒");
    }

    public List<ExcelDTO> getExcelDTOList() {
        List<ExcelDTO> list = new ArrayList<>();
        Stream.iterate(1, item -> item + 1).limit(10000)
                .forEach(item -> list.add(new ExcelDTO("king1", 15, "1123", new Date(), 111d, true)));
        return list;
    }

}

导出的效果图:

有属性转换的效果:

有属性转换的效果

无属性转换的效果:

无属性转换的效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值