【原创】POI操作Excel导入导出工具类ExcelUtil

关于本类线程安全性的解释:
多数工具方法不涉及共享变量问题,至于添加合并单元格方法addMergeArea,使用ThreadLocal变量存储合并数据,ThreadLocal内部借用Thread.ThreadLocalMap以当前ThreadLocal为key进行存储,设置一次变量,则其他线程也会有上次数据的残留,因此在addMergeArea方法中进行清空的操作。为了保证原子性, 采用ReentrantLock确保一次只有一个线程可以进行添加合并数据的操作。
线程安全性从以上两个方面保证。
水平有限,难免会有疏漏,敬请谅解。
主要使用Apache POI进行Excel的导入、导出

使用

读取Excel中的数据

原始数据如下:

待读取数据.png

方法:public static List<List<String>> readFile(InputStream ins, int headRowNum) throws Exception

使用方式:

        String filePath = "excel文件路径";
        File file = new File(filePath);
        InputStream ins = new FileInputStream(file);
        List<List<String>> lists = ExcelUtil.readFile(ins,  2);
        System.out.println(lists);

返回结果:

[[序号, 部门, 姓名, 上岗证号, 岗职, 入职时间], [, , , , , ], [1, 财务部, 赵六, 001, 出纳, 1540915200000], [1, 财务部, 张三, 002, 会计, 1517328000000]]
注:对于Date类型,读取时读的是long类型参数

将Excel中的数据转换为对应的实体Entity

  • public static <T> List<T> getListFromExcel(InputStream ins, int headRowNum, Class<T> cls, int parseIndex, String... excludeAttr) throws Exception
  • public static <T> List<T> getListFromExcel(MultipartFile file, int headRowNum, Class<T> cls, int parseIndex, String... excludeAttr) throws Exception

    两种方法本质上没有什么区别,可变参数excludeAttr配置实体不匹配的属性,例如:id
    注:转换的时候,需要保证excel中属性的顺序与实体中属性的顺序对应,例如excel中部门-姓名-上岗证号...这样,则实体也应该按照这样的顺序定义属性。

此方法通常用于获取对应的excel数据,并批量插入数据库中。

导出Excel

根据List数据导出excel
  • public static void exportExcel(String title, String[] headers, List<?> list, HttpServletResponse response, boolean useXSSF, String sheetName, List<String> includeAttr)

    title:导出名字
    headers:表头数组,list:数据, useXSSF:是否使用2007Excel, sheetName:创建sheet名字,includeAttr:展示实体的哪些属性

    public static void exportExcel(String title, String[] headers, List<?> list, HttpServletResponse response, boolean useXSSF, String sheetName, String... excludeAttr)主要差别在最后一个,不展示哪些属性



    如果在导出时需要合并单元格,先调用public static void setMergeAreaList(List<RectangleArea> list)
    例如:

   List<ExcelUtil.RectangleArea> list = new ArrayList<>();
   ExcelUtil.RectangleArea area = new ExcelUtil.RectangleArea(2,2,0,4);
   list.add(area);
   ExcelUtil.setMergeAreaList(list);
根据字符串列表导出excel

public static void exportExcel(String title, List<List<String>> list, boolean useXSSF, HttpServletResponse response)

根据字符串列表自定义导出实现

调用
public static Workbook getExcelWorkBook(List<List<String>> list, boolean useXSSF)获取workbook,
public static void addMergeArea(Workbook workbook, int sheetIndex, List<RectangleArea> areas)添加合并单元格数据,然后workboo.write(输出流)即可。
其余依赖工具类,请参见github地址:
https://github.com/studentytj/sth_useful

package com.test.sth_useful.common.util;


import com.test.sth_useful.common.util.ReflectionUtils;
import com.test.sth_useful.common.util.StreamUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.formula.functions.T;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.locks.ReentrantLock;

import static org.apache.poi.ss.usermodel.Cell.*;

/**
 * 关于本类线程安全性的解释:
 * 多数工具方法不涉及共享变量问题,至于添加合并单元格方法addMergeArea,使用ThreadLocal变量存储合并数据,ThreadLocal
 * 内部借用Thread.ThreadLocalMap以当前ThreadLocal为key进行存储,设置一次变量,则其他线程也会有上次数据的残留,因此在
 * addMergeArea方法中进行清空的操作。为了保证原子性, 采用ReentrantLock确保一次只有一个线程可以进行添加合并数据的操作。
 * 线程安全性从以上两个方面保证。
 * 水平有限,难免会有疏漏,敬请谅解。
 *
 *
 * @date 2018/10/15
 */
public class ExcelUtil {
    /**
     * logger
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(ExcelUtil.class);

    private static ThreadLocal<List<RectangleArea>> mergeAreaList = new ThreadLocal<>();

    public static List<RectangleArea> getMergeAreaList() {
        return mergeAreaList.get();
    }

    public static void setMergeAreaList(List<RectangleArea> list) {
        mergeAreaList.set(list);
    }

    /**
     * 获取单元格的值
     *
     * @param cell
     * @return
     */
    public static String getCellValue(Cell cell) {
        String cellValue = "";
        int cellType = cell.getCellType();
        if (cellType == CELL_TYPE_STRING) {
            cellValue = cell.getStringCellValue();
        } else if (cellType == CELL_TYPE_NUMERIC) {
            if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
                LOGGER.info("date: {}", cell.getDateCellValue());
                cellValue = String.valueOf(cell.getDateCellValue().getTime());
            } else {
                if (cell instanceof XSSFCell) {
                    cellValue = ((XSSFCell) cell).getRawValue();
                } else {
                    cellValue = String.valueOf(cell.getNumericCellValue());
                }
//                cellValue = String.valueOf(df.format(cell.getNumericCellValue()));
            }
        } else if (cellType == CELL_TYPE_FORMULA) {
            switch (cell.getCachedFormulaResultType()) {
                case Cell.CELL_TYPE_NUMERIC:
                    // 保留两位小数
                    cellValue = String.valueOf(new DecimalFormat("#.00").format(cell.getNumericCellValue()));
                    break;
                case Cell.CELL_TYPE_STRING:
                    cellValue = String.valueOf(cell.getRichStringCellValue());
                    break;
            }
        } else if (cellType == CELL_TYPE_BLANK) {
            cellValue = cell.getStringCellValue();
        }
        return cellValue;
    }

    /**
     * 读取excel
     * 注意: 默认读取的数据是在第一个sheet中的,暂不支持多个sheet的读取
     *
     * @param ins        输入流
     * @param headRowNum 表头所在行数
     * @return 返回excel中的字符串数据
     * @throws Exception
     */
    public static List<List<String>> readFile(InputStream ins, int headRowNum) throws Exception {
        Workbook workbook = WorkbookFactory.create(ins);
        Sheet sheet = workbook.getSheetAt(0);
        // 获取excel总行数
        int rownum = sheet.getLastRowNum();
        List<List<String>> result = new ArrayList<>();
        LOGGER.info("excel 总行数:{}", rownum);
        // 获取表头那一行的数据长度
        short allColumnNum = sheet.getRow(headRowNum).getLastCellNum();

        for (int i = 0; i <= rownum; i++) {
            Row row = sheet.getRow(i);
            if (row == null || row.getPhysicalNumberOfCells() == 0) {
                LOGGER.info("第{}行数据为空.", i);
                continue;
            }
            List<String> oneRecord = new ArrayList<>();
            for (int j = 0; j < allColumnNum; j++) {
                Cell cell = row.getCell(j);
//                cell.setCellStyle(cellStyle);
                if (cell == null) {
                    oneRecord.add("");
                    continue;
                }
                String cellValue = getCellValue(cell);
                oneRecord.add(cellValue);
            }
            result.add(oneRecord);
        }
        LOGGER.info("读取文件完成!");
        return result;
    }

    /**
     * 将读取到的excel数据存入List<cls>列表中
     *
     * @param list        字符串
     * @param cls         解析的类对象
     * @param parseIndex  开始解析的行数
     * @param excludeAttr 哪些属性不解析
     * @return 对应的对象列表
     * @throws Exception
     */
    public static <T> List<T> getEntityList(List<List<String>> list, Class<T> cls, int parseIndex, String... excludeAttr) throws Exception {
        List<T> result = new ArrayList<>();
        for (int i = parseIndex; i < list.size(); i++) {
            List<String> oneRecord = list.get(i);
            T instance = cls.newInstance();
            for (int j = 0, tmp = 0; j < oneRecord.size(); j++) {
                String cellValue = oneRecord.get(j);
                if (StringUtils.isEmpty(cellValue)) continue;
                Field[] fields = cls.getDeclaredFields();
                Field field = fields[j];
                for (String s : excludeAttr) {
                    if (field.getName().equals(s)) {
                        tmp++;
                        break;
                    }
                }
                field = fields[j + tmp];
                Object cellVal = field.getType().equals(Date.class) ? new Date(Long.parseLong(cellValue)) : cellValue;
                ReflectionUtils.setFieldValue(instance, field.getName(), cellVal);
            }
            result.add(instance);
        }
        return result;
    }

    /**
     * 获取excel表中对应的实体数据
     *
     * @param ins
     * @param headRowNum
     * @param cls
     * @param parseIndex
     * @param excludeAttr
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> List<T> getListFromExcel(InputStream ins, int headRowNum, Class<T> cls, int parseIndex, String... excludeAttr) throws Exception {
        List<List<String>> strLists = readFile(ins, headRowNum);
        return getEntityList(strLists, cls, parseIndex, excludeAttr);
    }

    /**
     * 获取excel对应的实体列表
     *
     * @param file        导入的excel文件
     * @param headRowNum  表头行数,记住:从0开始
     * @param cls         实体类
     * @param parseIndex  从哪一行开始解析
     * @param excludeAttr 哪些属性不映射
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> List<T> getListFromExcel(MultipartFile file, int headRowNum, Class<T> cls, int parseIndex, String... excludeAttr) throws Exception {
        // try(流)是Java7中的try-with-resource语法。当try语句块运行结束时,InputStream 会被自动关闭,只要实现了AutoCloseable接口的类都可以写在try括号里
        try (InputStream ins = file.getInputStream()) {
            List<List<String>> strLists = readFile(ins, headRowNum);
            return getEntityList(strLists, cls, parseIndex, excludeAttr);
        }
    }


    /**
     * 展示某些属性的方法
     *
     * @param headers
     * @param list
     * @param useXSSF     是否使用2007
     * @param sheetName
     * @param includeAttr
     * @param isInclude
     * @return
     */
    private static <T> Workbook getExcelWorkBook(String[] headers, List<T> list, boolean useXSSF, String sheetName, List<String> includeAttr, boolean isInclude) {
        Workbook workbook = getWorkbook(useXSSF);
        // 创建sheet
        Sheet sheet = workbook.createSheet(sheetName);
        CellStyle cellStyle = getCellStyle(workbook);
        // 创建表头
        createHeader(headers, sheet, useXSSF, cellStyle);
        Field[] fields = null;
        // 确定行数
        for (int i = 0; i < list.size(); i++) {
            // 创建一行
            Row dataRow = sheet.createRow(i + 1);
            T t = list.get(i);
            if (isInclude) {
                // 为每个单元格设值
                for (int j = 0; j < includeAttr.size(); j++) {
                    String fieldName = includeAttr.get(j);
                    formatCell(sheet, cellStyle, dataRow, t, j, fieldName);
                }
            } else {
                fields = fields == null ? t.getClass().getDeclaredFields() : fields;
                // k用来解决某些字段并不在excel中展示带来的数据和列名不匹配问题
                for (int j = 0, k = 0; j < fields.length - 1; j++) {
                    Field field = fields[j];
                    String fieldName = field.getName();
                    // flag标志代表是否不展示该项的值
                    boolean flag = false;
                    if (includeAttr.contains(fieldName)) {
                        // 解决生成数据不匹配问题
                        k++;
                        flag = true;
                        break;
                    }
                    if (flag) continue;

                    formatCell(sheet, cellStyle, dataRow, t, j - k, fieldName);
                }
            }
        }
        return workbook;
    }

    /**
     * 格式化单元格
     * @param sheet
     * @param cellStyle
     * @param dataRow
     * @param t
     * @param j
     * @param fieldName
     * @param <T>
     */
    private static <T> void formatCell(Sheet sheet, CellStyle cellStyle, Row dataRow, T t, int j, String fieldName) {
        Cell dataCell = dataRow.createCell(j);
        dataCell.setCellStyle(cellStyle);
        // 设置固定宽度, 每个单元格可存放16个字符
        sheet.setColumnWidth(j, 16 * 256);
        setCellValue(dataCell, ReflectionUtils.getFieldValue(t, fieldName));
    }

    /**
     * 获取通用style
     *
     * @param workbook
     * @return
     */
    private static CellStyle getCellStyle(Workbook workbook) {
        CellStyle cellStyle = workbook.createCellStyle();
        cellStyle.setBorderLeft(CellStyle.BORDER_THIN);
        cellStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
        cellStyle.setBorderRight(CellStyle.BORDER_THIN);
        cellStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
        cellStyle.setBorderBottom(CellStyle.BORDER_THIN);
        cellStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
        cellStyle.setBorderTop(CellStyle.BORDER_THIN);
        cellStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
        return cellStyle;
    }

    private static Workbook getWorkbook(boolean useXSSF) {
        return useXSSF ? new XSSFWorkbook() : new HSSFWorkbook();
    }

    /**
     * 给出字符串的情况下获取WorkBook
     *
     * @param list
     * @return
     */
    public static Workbook getExcelWorkBook(List<List<String>> list, boolean useXSSF) {
        Workbook workbook = getWorkbook(useXSSF);
        // 创建sheet, 名字默认为sheet0
        Sheet sheet = workbook.createSheet("sheet0");
        // 确定行数
        for (int i = 0; i < list.size(); i++) {
            // 创建一行
            Row dataRow = sheet.createRow(i);
            List<String> t = list.get(i);
            // 为每个单元格设值
            for (int j = 0; j < t.size(); j++) {
                Cell dataCell = dataRow.createCell(j);
                // 设置固定宽度, 每个单元格可存放16个字符
                setCellValue(dataCell, t.get(j));
            }
        }
        return workbook;
    }

    /**
     * 创建表头
     *
     * @param headers
     * @param sheet
     */
    private static void createHeader(String[] headers, Sheet sheet, boolean useXSSF, CellStyle commonStyle) {
        Row headerRow = sheet.createRow(0);
        for (int i = 0; i < headers.length; i++) {
            Cell headerRowCell = headerRow.createCell(i);
            headerRowCell.setCellStyle(commonStyle);
            RichTextString text = useXSSF ? new XSSFRichTextString(headers[i]) : new HSSFRichTextString(headers[i]);
            headerRowCell.setCellValue(text);
        }
    }

    /**
     * 为单元格设置值
     *
     * @param dataCell
     * @param value
     */
    public static void setCellValue(Cell dataCell, Object value) {
        if (value instanceof Date) {
            DateFormat sdf = DateUtil.secondFormat.get();
            String zero = "00:00:00";
            if (zero.equals(sdf.format(value).split(" ")[1])) {
                sdf = DateUtil.dayFormat.get();
            }
            dataCell.setCellValue(sdf.format(value));
        } else if (value instanceof Number) {
            dataCell.setCellValue(Double.parseDouble(String.valueOf(value)));
        } else if (value == null) {
            dataCell.setCellValue("");
        } else {
            dataCell.setCellValue(value.toString());
        }
    }


    /**
     * 导出excel ——展示某些属性
     *
     * @param title
     * @param headers
     * @param list
     * @param response
     * @param useXSSF
     * @param sheetName
     * @param includeAttr
     */
    public static void exportExcel(String title, String[] headers, List<?> list, HttpServletResponse response, boolean useXSSF, String sheetName, List<String> includeAttr, boolean isInclude) {
        try {
            Workbook workbook = getExcelWorkBook(headers, list, useXSSF, sheetName, includeAttr, isInclude);
            addMergeArea(workbook, 0, getMergeAreaList());
            writeToResp(title, workbook, response, useXSSF);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 给出导出字符串的情况下的导出方法
     *
     * @param title
     * @param list
     * @param response
     */
    public static void exportExcel(String title, List<List<String>> list, boolean useXSSF, HttpServletResponse response) {
        try {
            Workbook workbook = getExcelWorkBook(list, useXSSF);
            writeToResp(title, workbook, response, useXSSF);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 将excel流写入response
     *
     * @param title
     * @param workbook
     * @param response
     * @param useXSSF
     */
    private static void writeToResp(String title, Workbook workbook, HttpServletResponse response, boolean useXSSF) {
        ServletOutputStream outputStream = null;
        try {
            LOGGER.info("" + workbook);
            outputStream = response.getOutputStream();
            // 解决浏览器及中文乱码问题https://tools.ietf.org/html/rfc2231
            response.setHeader("Content-Disposition", "attachment;filename*=utf-8'zh_cn'" + URLEncoder.encode(title, "UTF-8") + (useXSSF ? ".xlsx" : ".xls"));
            response.setContentType("application/msexcel");
            workbook.write(outputStream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭流资源
            StreamUtil.close(outputStream, workbook);
        }
    }

    /**
     * 合并单元格并水平垂直居中实现
     *
     * @param areas
     */
    public static void addMergeArea(Workbook workbook, int sheetIndex, List<RectangleArea> areas) {
        ReentrantLock lock = new ReentrantLock();
        try {
            lock.lock();
            LOGGER.info("Thread: {}, threadLocal is: {}.", Thread.currentThread().getName(), String.valueOf(getMergeAreaList()));
            if (areas != null && areas.size() > 0) {
                for (RectangleArea area : areas) {
                    workbook.getSheetAt(sheetIndex)
                            .addMergedRegion(new CellRangeAddress(area.getFirstRow(), area.getLastRow(), area.getFirstCol(), area.getLastCol()));
                    // 合并单元格时默认居中(水平、垂直居中)
                    CellStyle cellStyle = getCellStyle(workbook);
                    cellStyle.setAlignment(CellStyle.ALIGN_CENTER);
                    cellStyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
                    workbook.getSheetAt(sheetIndex).getRow(area.getFirstRow()).getCell(area.getFirstCol()).setCellStyle(cellStyle);
                }
                mergeAreaList.set(null);
            }
        } finally {
            lock.unlock();
        }
    }

    public static class RectangleArea {
        private int firstRow, lastRow, firstCol, lastCol;

        public RectangleArea(int firstRow, int lastRow, int firstCol, int lastCol) {
            this.firstRow = firstRow;
            this.lastRow = lastRow;
            this.firstCol = firstCol;
            this.lastCol = lastCol;
        }

        public int getFirstRow() {
            return firstRow;
        }

        public int getLastRow() {
            return lastRow;
        }

        public int getFirstCol() {
            return firstCol;
        }

        public int getLastCol() {
            return lastCol;
        }
    }

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 【程序1】 题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一...
    阿里高级软件架构师阅读 3,277评论 0 19
  • 废话不多说,自己进入今天的主题 1、面向对象的特征有哪些方面? 答:面向对象的特征主要有以下几个方面: - 抽象:...
    传奇内服号阅读 2,333评论 1 31
  • 你一定是幻想过这样的生活:“在满地秋叶的某个小镇,那里一年四季没有极寒,没有酷热,时而阳光明媚,时而阴雨连绵,日子...
    仙女刚下凡yeah阅读 319评论 0 2
  • 前些日子里,我失恋了,和大多数人一样,蒙在被子里面哭,到走廊打电话找人倾诉,告诉自己过去了,然后忘却一切,看着手边...
    小摘阅读 224评论 0 0