Repository: wangguanquan/eec
Pull request #76 (2019-08-20 13:39:44)

Fix#71
Change the value obtained from field to method to get the value


Patch:
diff --git a/.travis.yml b/.travis.yml
index 67017dbd..afd4c80d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,3 @@
 language: java
 
-script: mvn clean test
\ No newline at end of file
+script: mvn clean test clean
\ No newline at end of file
diff --git a/src/main/java/org/ttzero/excel/annotation/ExcelColumn.java b/src/main/java/org/ttzero/excel/annotation/ExcelColumn.java
index 3d162b00..d53f2ec9 100644
--- a/src/main/java/org/ttzero/excel/annotation/ExcelColumn.java
+++ b/src/main/java/org/ttzero/excel/annotation/ExcelColumn.java
@@ -28,7 +28,7 @@ import java.lang.annotation.Target;
  * <p>
  * Create by guanquan.wang at 2019-06-21 09:53
  */
-@Target({ElementType.FIELD})
+@Target({ ElementType.FIELD, ElementType.METHOD })
 @Retention(RetentionPolicy.RUNTIME)
 @Inherited
 @Documented
diff --git a/src/main/java/org/ttzero/excel/annotation/IgnoreExport.java b/src/main/java/org/ttzero/excel/annotation/IgnoreExport.java
index 1f96f41c..5bf6b987 100644
--- a/src/main/java/org/ttzero/excel/annotation/IgnoreExport.java
+++ b/src/main/java/org/ttzero/excel/annotation/IgnoreExport.java
@@ -19,6 +19,7 @@ package org.ttzero.excel.annotation;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
@@ -28,8 +29,9 @@ import java.lang.annotation.Target;
  * <p>
  * Created by guanquan.wang at 2018-01-30 15:09
  */
-@Target({ElementType.FIELD})
+@Target({ ElementType.FIELD, ElementType.METHOD })
 @Retention(RetentionPolicy.RUNTIME)
+@Inherited
 @Documented
 public @interface IgnoreExport {
     /**
diff --git a/src/main/java/org/ttzero/excel/annotation/IgnoreImport.java b/src/main/java/org/ttzero/excel/annotation/IgnoreImport.java
index 3d66f873..16593b96 100644
--- a/src/main/java/org/ttzero/excel/annotation/IgnoreImport.java
+++ b/src/main/java/org/ttzero/excel/annotation/IgnoreImport.java
@@ -18,6 +18,7 @@ package org.ttzero.excel.annotation;
 
 import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
@@ -27,8 +28,9 @@ import java.lang.annotation.Target;
  * <p>
  * Created by guanquan.wang at 2018-10-24 09:29
  */
-@Target({ElementType.FIELD})
+@Target({ ElementType.FIELD, ElementType.METHOD })
 @Retention(RetentionPolicy.RUNTIME)
+@Inherited
 @Documented
 public @interface IgnoreImport {
     /**
diff --git a/src/main/java/org/ttzero/excel/entity/ListSheet.java b/src/main/java/org/ttzero/excel/entity/ListSheet.java
index 1122783e..a697ba2e 100644
--- a/src/main/java/org/ttzero/excel/entity/ListSheet.java
+++ b/src/main/java/org/ttzero/excel/entity/ListSheet.java
@@ -22,11 +22,23 @@ import org.ttzero.excel.annotation.DisplayName;
 import org.ttzero.excel.annotation.IgnoreExport;
 import org.ttzero.excel.util.StringUtil;
 
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
 import java.io.IOException;
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
+import static org.ttzero.excel.util.ReflectUtil.indexOf;
+import static org.ttzero.excel.util.ReflectUtil.listDeclaredFields;
+import static org.ttzero.excel.util.ReflectUtil.listReadMethods;
+import static org.ttzero.excel.util.ReflectUtil.mapping;
 import static org.ttzero.excel.util.StringUtil.isNotEmpty;
 
 /**
@@ -44,6 +56,7 @@ import static org.ttzero.excel.util.StringUtil.isNotEmpty;
 public class ListSheet<T> extends Sheet {
     protected List<T> data;
     private Field[] fields;
+    private Method[] methods;
     protected int start, end;
     protected boolean eof;
     private int size;
@@ -231,20 +244,23 @@ public class ListSheet<T> extends Sheet {
             for (; start < end; rows++, start++) {
                 Row row = rowBlock.next();
                 row.index = rows;
-                Field field;
                 Cell[] cells = row.realloc(len);
+                T o = data.get(start);
                 for (int i = 0; i < len; i++) {
-                    field = fields[i];
                     // clear cells
                     Cell cell = cells[i];
                     cell.clear();
 
-                    Object e = field.get(data.get(start));
+                    Object e;
+                    if (methods[i] != null)
+                        e = methods[i].invoke(o);
+                    else
+                        e = fields[i].get(o);
 
                     setCellValueAndStyle(cell, e, columns[i]);
                 }
             }
-        } catch (IllegalAccessException e) {
+        } catch (IllegalAccessException | InvocationTargetException e) {
             throw new ExcelWriteException(e);
         }
     }
@@ -296,6 +312,42 @@ public class ListSheet<T> extends Sheet {
 
     private static final String[] exclude = {"serialVersionUID", "this$0"};
 
+    // Returns the reflect <T> type
+    private Class<?> getTClass() {
+        Class<?> clazz;
+        if (getClass().getGenericSuperclass() instanceof  ParameterizedType) {
+            @SuppressWarnings({"unchecked", "retype"})
+            Class<?> tClass = (Class<T>) ((ParameterizedType) getClass()
+                .getGenericSuperclass()).getActualTypeArguments()[0];
+            clazz = tClass;
+        } else {
+            T o = getFirst();
+            if (o == null) return null;
+            clazz = o.getClass();
+        }
+        return clazz;
+    }
+
+    private int methodMapping(Class<?> clazz, Method[] readMethods, Map<String, Method> tmp) {
+        try {
+            PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(clazz)
+                .getPropertyDescriptors();
+            Method[] allMethods = clazz.getMethods()
+                , mergedMethods = new Method[propertyDescriptors.length];
+            for (int i = 0; i < propertyDescriptors.length; i++) {
+                Method method = propertyDescriptors[i].getReadMethod();
+                if (method == null) continue;
+                int index = indexOf(allMethods, method);
+                mergedMethods[i] = index >= 0 ? allMethods[index] : method;
+            }
+
+            return mapping(readMethods, tmp, propertyDescriptors, mergedMethods);
+        } catch (IntrospectionException e) {
+            what("Get " + clazz + " property descriptor failed.");
+        }
+        return 0;
+    }
+
     /**
      * Get the first object of the object array witch is not NULL,
      * reflect all declared fields, and then do the following steps
@@ -306,73 +358,141 @@ public class ListSheet<T> extends Sheet {
      * step 2. If the {@link ExcelColumn} annotation has no value or no
      * {@link ExcelColumn} annotation, the field name is used as the column name.
      * <p>
-     * step 3. Skip this Field if field has a {@link IgnoreExport} annotation
+     * step 3. Skip this Field if field has a {@link IgnoreExport} annotation,
+     * or the field which has not getter method and has not {@link ExcelColumn} annotation.
      * <p>
      * The column order is the same as the order in declared fields.
      *
      * @return the field array
      */
-    private Field[] init() {
-        // Get the first not NULL object
-        T o = getFirst();
-        if (o == null) return null;
+    private int init() {
+        Class<?> clazz = getTClass();
+        if (clazz == null) return 0;
+
+        // The main source
+        Field[] declaredFields = listDeclaredFields(clazz);
+
+        Method[] readMethods = null;
+        try {
+            readMethods = listReadMethods(clazz, method -> method.getAnnotation(ExcelColumn.class) != null);
+        } catch (IntrospectionException e) {
+            what("Get " + clazz + " read declared failed.");
+        }
+
+        Map<String, Method> tmp = new LinkedHashMap<>();
+
+        int readLength = methodMapping(clazz, readMethods, tmp);
+
         if (!hasHeaderColumns()) {
-            Field[] fields = o.getClass().getDeclaredFields();
-            List<Column> list = new ArrayList<>(fields.length);
-            for (int i = 0; i < fields.length; i++) {
-                Field field = fields[i];
-                String gs = field.toGenericString();
+            // Get ExcelColumn annotation method
+            methods = new Method[declaredFields.length + readLength];
+            List<Column> list = new ArrayList<>(declaredFields.length);
+            int i = 0;
+            for (; i < declaredFields.length; i++) {
+                Field field = declaredFields[i];
+                field.setAccessible(true);
+                String gs = field.getName();
+
+                // Ignore annotation on read method
+                Method method = tmp.get(gs);
+                if (method != null) {
+                    if (method.getAnnotation(IgnoreExport.class) != null) {
+                        declaredFields[i] = null;
+                        continue;
+                    }
+
+                    methods[i] = method;
+                    ExcelColumn mec = method.getAnnotation(ExcelColumn.class);
+                    if (mec != null && isNotEmpty(mec.value())) {
+                        list.add(new Column(mec.value(), field.getName(), method.getReturnType())
+                            .setShare(mec.share()));
+                        continue;
+                    }
+                }
+
+                // Ignore annotation on field
                 IgnoreExport notExport = field.getAnnotation(IgnoreExport.class);
                 if (notExport != null || StringUtil.indexOf(exclude, gs.substring(gs.lastIndexOf('.') + 1)) >= 0) {
-                    fields[i] = null;
+                    declaredFields[i] = null;
                     continue;
                 }
-                DisplayName dn = field.getAnnotation(DisplayName.class);
+
                 ExcelColumn ec = field.getAnnotation(ExcelColumn.class);
+                DisplayName dn = field.getAnnotation(DisplayName.class);
+
                 if (ec != null && isNotEmpty(ec.value())) {
-                    list.add(new Column(ec.value(), field.getName()
-                        , field.getType()).setShare(ec.share()));
+                    list.add(new Column(ec.value(), field.getName(), field.getType())
+                        .setShare(ec.share()));
                 } else if (dn != null && isNotEmpty(dn.value())) {
-                    list.add(new Column(dn.value(), field.getName()
-                        , field.getType()).setShare(dn.share()));
-                } else {
-                    list.add(new Column(field.getName(), field.getName()
-                        , field.getType()).setShare(dn == null || dn.share()));
+                    list.add(new Column(dn.value(), field.getName(), field.getType())
+                        .setShare(dn.share()));
+                } else if (method != null) {
+                    list.add(new Column(field.getName(), field.getName(), field.getType())
+                        .setShare(dn == null || dn.share()));
                 }
             }
+            if (readLength > 0) {
+                for (int j = 0; j < readLength; j++) {
+                    Method readMethod = readMethods[j];
+                    methods[i++] = readMethod;
+                    ExcelColumn mec = readMethod.getAnnotation(ExcelColumn.class);
+                    list.add(new Column(mec.value(), readMethod.getName(), readMethod.getReturnType())
+                        .setShare(mec.share()));
+                }
+            }
+            // No column to write
+            if (list.isEmpty()) {
+                headerReady = eof = shouldClose = true;
+                this.end = 0;
+                what("Class [" + clazz + "] do not contains getter method and ExcelColumn annotation.");
+                return 0;
+            }
             columns = new Column[list.size()];
             list.toArray(columns);
-            for (int i = 0; i < columns.length; i++) {
+            for (i = 0; i < columns.length; i++) {
                 columns[i].styles = workbook.getStyles();
             }
-            // clear not export fields
-            for (int len = fields.length, n = len - 1; n >= 0; n--) {
-                if (fields[n] != null) {
-                    fields[n].setAccessible(true);
-                    continue;
-                }
-                if (n < len - 1) {
-                    System.arraycopy(fields, n + 1, fields, n, len - n - 1);
+            // Clear not export fields
+            i = 0;
+            fields = new Field[columns.length];
+            for (int j = 0; j < declaredFields.length; j++) {
+                if (declaredFields[j] != null) {
+                    declaredFields[j].setAccessible(true);
+                    fields[i] = declaredFields[j];
+                    methods[i] = methods[j];
+                    i++;
                 }
-                len--;
             }
-            return fields;
+            if (declaredFields.length < methods.length) {
+                System.arraycopy(methods, declaredFields.length, methods, i, methods.length - declaredFields.length);
+                i += methods.length - declaredFields.length;
+            }
+            return i + readLength;
         } else {
-            Field[] fields = new Field[columns.length];
-            Class<?> clazz = o.getClass();
+            fields = new Field[columns.length];
+            methods = new Method[columns.length];
             for (int i = 0; i < columns.length; i++) {
                 Column hc = columns[i];
-                try {
-                    fields[i] = clazz.getDeclaredField(hc.key);
-                    fields[i].setAccessible(true);
-                    if (hc.getClazz() == null) {
-                        hc.setClazz(fields[i].getType());
+                methods[i] = tmp.get(hc.key);
+
+                for (Field field : declaredFields) {
+                    if (hc.key.equals(field.getName())) {
+                        field.setAccessible(true);
+                        fields[i] = field;
+                        break;
                     }
-                } catch (NoSuchFieldException e) {
-                    throw new ExcelWriteException("Column " + hc.getName() + " not declare in class " + clazz);
+                }
+
+                if (methods[i] == null && fields[i] == null) {
+                    throw new ExcelWriteException("Column [" + hc.getName() + "(" + hc.key + ")"
+                        + "] not declare in class " + clazz);
+                }
+
+                if (hc.getClazz() == null) {
+                    hc.setClazz(methods[i] != null ? methods[i].getReturnType() : fields[i].getType());
                 }
             }
-            return fields;
+            return columns.length;
         }
 
     }
@@ -389,8 +509,8 @@ public class ListSheet<T> extends Sheet {
 //                columns = new Column[0];
 //            }
             // create header columns
-            fields = init();
-            if (fields == null || fields.length == 0 || fields[0] == null) {
+            int size = init();
+            if (size <= 0) {
                 columns = new Column[0];
             } else {
                 // Check the header column limit
diff --git a/src/main/java/org/ttzero/excel/entity/e7/XMLWorksheetWriter.java b/src/main/java/org/ttzero/excel/entity/e7/XMLWorksheetWriter.java
index 928d6eb5..b351b847 100644
--- a/src/main/java/org/ttzero/excel/entity/e7/XMLWorksheetWriter.java
+++ b/src/main/java/org/ttzero/excel/entity/e7/XMLWorksheetWriter.java
@@ -74,6 +74,7 @@ import static org.ttzero.excel.entity.IWorksheetWriter.isLong;
 import static org.ttzero.excel.entity.IWorksheetWriter.isLocalDate;
 import static org.ttzero.excel.entity.IWorksheetWriter.isLocalDateTime;
 import static org.ttzero.excel.entity.IWorksheetWriter.isLocalTime;
+import static org.ttzero.excel.util.StringUtil.isNotEmpty;
 
 /**
  * Create by guanquan.wang at 2019-04-22 16:31
@@ -396,7 +397,7 @@ public class XMLWorksheetWriter implements IWorksheetWriter {
             bw.write("\" t=\"s\" s=\"");
             bw.writeInt(defaultStyle);
             bw.write("\"><v>");
-            bw.writeInt(sst.get(hc.getName()));
+            bw.writeInt(sst.get(isNotEmpty(hc.getName()) ? hc.getName() : hc.key));
             bw.write("</v></c>");
         }
         bw.write("</row>");
diff --git a/src/main/java/org/ttzero/excel/reader/HeaderRow.java b/src/main/java/org/ttzero/excel/reader/HeaderRow.java
index 59064e80..6aba07ea 100644
--- a/src/main/java/org/ttzero/excel/reader/HeaderRow.java
+++ b/src/main/java/org/ttzero/excel/reader/HeaderRow.java
@@ -19,12 +19,24 @@ package org.ttzero.excel.reader;
 import org.ttzero.excel.annotation.DisplayName;
 import org.ttzero.excel.annotation.ExcelColumn;
 import org.ttzero.excel.annotation.IgnoreImport;
+import org.ttzero.excel.util.ReflectUtil;
 import org.ttzero.excel.util.StringUtil;
 
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.StringJoiner;
 
+import static org.ttzero.excel.util.ReflectUtil.listDeclaredFields;
+import static org.ttzero.excel.util.ReflectUtil.listWriteMethods;
+import static org.ttzero.excel.util.ReflectUtil.mapping;
+import static org.ttzero.excel.util.StringUtil.indexOf;
 import static org.ttzero.excel.util.StringUtil.isNotEmpty;
 
 /**
@@ -34,6 +46,7 @@ class HeaderRow extends Row {
     private String[] names;
     private Class<?> clazz;
     private Field[] fields;
+    private Method[] methods;
     private int[] columns;
     private Class<?>[] fieldClazz;
     private Object t;
@@ -74,65 +87,143 @@ class HeaderRow extends Row {
      */
     final HeaderRow setClass(Class<?> clazz) {
         this.clazz = clazz;
-        Field[] fields = clazz.getDeclaredFields();
-        int[] index = new int[fields.length];
+        Field[] declaredFields = listDeclaredFields(clazz);
+
+        Method[] writeMethods = null;
+        try {
+            writeMethods = listWriteMethods(clazz, method -> method.getAnnotation(ExcelColumn.class) != null);
+        } catch (IntrospectionException e) {
+            logger.warn("Get [" + clazz + "] read declared failed.", e);
+        }
+
+        Map<String, Method> tmp = new LinkedHashMap<>();
+
+        int writeLength = methodMapping(clazz, writeMethods, tmp);
+        methods = new Method[declaredFields.length + writeLength];
+
+
+        int[] index = new int[declaredFields.length];
         int count = 0;
-        for (int i = 0, n; i < fields.length; i++) {
-            Field f = fields[i];
+        for (int i = 0, n; i < declaredFields.length; i++) {
+            Field f = declaredFields[i];
+            f.setAccessible(true);
+            String gs = f.getName();
+
+            // Ignore annotation on read method
+            Method method = tmp.get(gs);
+            if (method != null) {
+                if (method.getAnnotation(IgnoreImport.class) != null) {
+                    declaredFields[i] = null;
+                    continue;
+                }
+
+                methods[i] = method;
+                ExcelColumn mec = method.getAnnotation(ExcelColumn.class);
+                if (mec != null && isNotEmpty(mec.value())) {
+                    n = check(mec.value(), gs);
+                    if (n == -1) {
+                        declaredFields[i] = null;
+                    } else {
+                        index[i] = n;
+                        count++;
+                    }
+                    continue;
+                }
+            }
+
             // skip not import fields
             IgnoreImport nit = f.getAnnotation(IgnoreImport.class);
             if (nit != null) {
-                fields[i] = null;
+                declaredFields[i] = null;
                 continue;
             }
             // field has display name
-            DisplayName ano = f.getAnnotation(DisplayName.class);
             ExcelColumn ec = f.getAnnotation(ExcelColumn.class);
+            DisplayName ano = f.getAnnotation(DisplayName.class);
             if (ec != null && isNotEmpty(ec.value())) {
-                n = StringUtil.indexOf(names, ec.value());
+                n = check(ec.value(), gs);
                 if (n == -1) {
-                    logger.warn(clazz + " field [" + ec.value() + "] can't find in header" + Arrays.toString(names));
-                    fields[i] = null;
+                    declaredFields[i] = null;
                     continue;
                 }
             } else if (ano != null && isNotEmpty(ano.value())) {
-                n = StringUtil.indexOf(names, ano.value());
+                n = check(ano.value(), gs);
                 if (n == -1) {
-                    logger.warn(clazz + " field [" + ano.value() + "] can't find in header" + Arrays.toString(names));
-                    fields[i] = null;
+                    declaredFields[i] = null;
                     continue;
                 }
             }
-            // no annotation or annotation value is null
-            else {
+            // Annotation value is null
+            else if (ec != null || ano != null || methods[i] != null) {
                 String name = f.getName();
-                n = StringUtil.indexOf(names, name);
-                if (n == -1 && (n = StringUtil.indexOf(names, StringUtil.toPascalCase(name))) == -1) {
-                    fields[i] = null;
+                n = indexOf(names, name);
+                if (n == -1 && (n = indexOf(names, StringUtil.toPascalCase(name))) == -1) {
+                    declaredFields[i] = null;
                     continue;
                 }
+            } else {
+                declaredFields[i] = null;
+                continue;
             }
 
             index[i] = n;
             count++;
         }
 
+        if (writeLength > 0) {
+            System.arraycopy(writeMethods, 0, methods, declaredFields.length, writeLength);
+            count += writeLength;
+//            for (int i = declaredFields.length, j = 0; j < writeLength; j++) {
+//                index[i++] =
+//            }
+        }
+
         this.fields = new Field[count];
         this.columns = new int[count];
         this.fieldClazz = new Class<?>[count];
 
-        for (int i = fields.length - 1; i >= 0; i--) {
-            if (fields[i] != null) {
-                count--;
-                this.fields[count] = fields[i];
-                this.fields[count].setAccessible(true);
-                this.columns[count] = index[i];
-                this.fieldClazz[count] = fields[i].getType();
+        for (int i = 0, j = 0; i < declaredFields.length; i++) {
+            if (declaredFields[i] != null) {
+                fields[j] = declaredFields[i];
+                columns[j] = index[i];
+                methods[j] = methods[i];
+                fieldClazz[j] = methods[i] != null ? methods[i].getParameterTypes()[0] : declaredFields[i].getType();
+                j++;
             }
         }
+
         return this;
     }
 
+    private int methodMapping(Class<?> clazz, Method[] writeMethods, Map<String, Method> tmp) {
+        try {
+            PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(clazz)
+                .getPropertyDescriptors();
+            Method[] allMethods = clazz.getMethods()
+                , mergedMethods = new Method[propertyDescriptors.length];
+            for (int i = 0; i < propertyDescriptors.length; i++) {
+                Method method = propertyDescriptors[i].getWriteMethod();
+                if (method == null) continue;
+                int index = ReflectUtil.indexOf(allMethods, method);
+                mergedMethods[i] = index >= 0 ? allMethods[index] : method;
+            }
+
+            return mapping(writeMethods, tmp, propertyDescriptors, mergedMethods);
+        } catch (IntrospectionException e) {
+            logger.warn("Get " + clazz + " property descriptor failed.");
+        }
+        return 0;
+    }
+
+    private int check(String first, String second) {
+        int n = indexOf(names, first);
+        if (n == -1) n = indexOf(names, second);
+        if (n == -1) {
+            logger.warn(clazz + " field [" + first + "] can't find in header" + Arrays.toString(names));
+        }
+        return n;
+    }
+
     /**
      * mapping and instance
      *
@@ -195,32 +286,66 @@ class HeaderRow extends Row {
         return joiner.toString();
     }
 
-    void put(Row row, Object t) throws IllegalAccessException {
+    void put(Row row, Object t) throws IllegalAccessException, InvocationTargetException {
         for (int i = 0; i < columns.length; i++) {
-            int c = columns[i];
-            if (fieldClazz[i] == String.class) {
-                fields[i].set(t, row.getString(c));
-            } else if (fieldClazz[i] == int.class || fieldClazz[i] == Integer.class) {
-                fields[i].set(t, row.getInt(c));
-            } else if (fieldClazz[i] == long.class || fieldClazz[i] == Long.class) {
-                fields[i].set(t, row.getLong(c));
-            } else if (fieldClazz[i] == java.util.Date.class || fieldClazz[i] == java.sql.Date.class) {
-                fields[i].set(t, row.getDate(c));
-            } else if (fieldClazz[i] == java.sql.Timestamp.class) {
-                fields[i].set(t, row.getTimestamp(c));
-            } else if (fieldClazz[i] == double.class || fieldClazz[i] == Double.class) {
-                fields[i].set(t, row.getDouble(c));
-            } else if (fieldClazz[i] == float.class || fieldClazz[i] == Float.class) {
-                fields[i].set(t, row.getFloat(c));
-            } else if (fieldClazz[i] == boolean.class || fieldClazz[i] == Boolean.class) {
-                fields[i].set(t, row.getBoolean(c));
-            } else if (fieldClazz[i] == char.class || fieldClazz[i] == Character.class) {
-                fields[i].set(t, row.getChar(c));
-            } else if (fieldClazz[i] == byte.class || fieldClazz[i] == Byte.class) {
-                fields[i].set(t, row.getByte(c));
-            } else if (fieldClazz[i] == short.class || fieldClazz[i] == Short.class) {
-                fields[i].set(t, row.getShort(c));
-            }
+            if (methods[i] != null)
+                methodPut(i, row, t);
+            else
+                fieldPut(i, row, t);
+        }
+    }
+
+    private void fieldPut(int i, Row row, Object t) throws IllegalAccessException {
+        int c = columns[i];
+        if (fieldClazz[i] == String.class) {
+            fields[i].set(t, row.getString(c));
+        } else if (fieldClazz[i] == int.class || fieldClazz[i] == Integer.class) {
+            fields[i].set(t, row.getInt(c));
+        } else if (fieldClazz[i] == long.class || fieldClazz[i] == Long.class) {
+            fields[i].set(t, row.getLong(c));
+        } else if (fieldClazz[i] == java.util.Date.class || fieldClazz[i] == java.sql.Date.class) {
+            fields[i].set(t, row.getDate(c));
+        } else if (fieldClazz[i] == java.sql.Timestamp.class) {
+            fields[i].set(t, row.getTimestamp(c));
+        } else if (fieldClazz[i] == double.class || fieldClazz[i] == Double.class) {
+            fields[i].set(t, row.getDouble(c));
+        } else if (fieldClazz[i] == float.class || fieldClazz[i] == Float.class) {
+            fields[i].set(t, row.getFloat(c));
+        } else if (fieldClazz[i] == boolean.class || fieldClazz[i] == Boolean.class) {
+            fields[i].set(t, row.getBoolean(c));
+        } else if (fieldClazz[i] == char.class || fieldClazz[i] == Character.class) {
+            fields[i].set(t, row.getChar(c));
+        } else if (fieldClazz[i] == byte.class || fieldClazz[i] == Byte.class) {
+            fields[i].set(t, row.getByte(c));
+        } else if (fieldClazz[i] == short.class || fieldClazz[i] == Short.class) {
+            fields[i].set(t, row.getShort(c));
+        }
+    }
+
+    private void methodPut(int i, Row row, Object t) throws IllegalAccessException, InvocationTargetException {
+        int c = columns[i];
+        if (fieldClazz[i] == String.class) {
+            methods[i].invoke(t, row.getString(c));
+        } else if (fieldClazz[i] == int.class || fieldClazz[i] == Integer.class) {
+            methods[i].invoke(t, row.getInt(c));
+        } else if (fieldClazz[i] == long.class || fieldClazz[i] == Long.class) {
+            methods[i].invoke(t, row.getLong(c));
+        } else if (fieldClazz[i] == java.util.Date.class || fieldClazz[i] == java.sql.Date.class) {
+            methods[i].invoke(t, row.getDate(c));
+        } else if (fieldClazz[i] == java.sql.Timestamp.class) {
+            methods[i].invoke(t, row.getTimestamp(c));
+        } else if (fieldClazz[i] == double.class || fieldClazz[i] == Double.class) {
+            methods[i].invoke(t, row.getDouble(c));
+        } else if (fieldClazz[i] == float.class || fieldClazz[i] == Float.class) {
+            methods[i].invoke(t, row.getFloat(c));
+        } else if (fieldClazz[i] == boolean.class || fieldClazz[i] == Boolean.class) {
+            methods[i].invoke(t, row.getBoolean(c));
+        } else if (fieldClazz[i] == char.class || fieldClazz[i] == Character.class) {
+            methods[i].invoke(t, row.getChar(c));
+        } else if (fieldClazz[i] == byte.class || fieldClazz[i] == Byte.class) {
+            methods[i].invoke(t, row.getByte(c));
+        } else if (fieldClazz[i] == short.class || fieldClazz[i] == Short.class) {
+            methods[i].invoke(t, row.getShort(c));
         }
     }
 }
diff --git a/src/main/java/org/ttzero/excel/reader/Row.java b/src/main/java/org/ttzero/excel/reader/Row.java
index 14115702..ee3654ac 100644
--- a/src/main/java/org/ttzero/excel/reader/Row.java
+++ b/src/main/java/org/ttzero/excel/reader/Row.java
@@ -22,6 +22,7 @@ import org.ttzero.excel.entity.style.Styles;
 import org.ttzero.excel.util.DateUtil;
 import org.ttzero.excel.util.StringUtil;
 
+import java.lang.reflect.InvocationTargetException;
 import java.math.BigDecimal;
 import java.sql.Timestamp;
 import java.util.Date;
@@ -587,7 +588,7 @@ public abstract class Row {
             try {
                 t = (T) hr.getClazz().newInstance();
                 hr.put(this, t);
-            } catch (InstantiationException | IllegalAccessException e) {
+            } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
                 throw new UncheckedTypeException(hr.getClazz() + " new instance error.", e);
             }
             return t;
@@ -606,7 +607,7 @@ public abstract class Row {
             T t = hr.getT();
             try {
                 hr.put(this, t);
-            } catch (IllegalAccessException e) {
+            } catch (IllegalAccessException | InvocationTargetException e) {
                 throw new UncheckedTypeException("call set method error.", e);
             }
             return t;
@@ -633,7 +634,7 @@ public abstract class Row {
         try {
             t = clazz.newInstance();
             hr.put(this, t);
-        } catch (InstantiationException | IllegalAccessException e) {
+        } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
             throw new UncheckedTypeException(clazz + " new instance error.", e);
         }
         return t;
@@ -662,7 +663,7 @@ public abstract class Row {
         T t = hr.getT();
         try {
             hr.put(this, t);
-        } catch (IllegalAccessException e) {
+        } catch (IllegalAccessException | InvocationTargetException e) {
             throw new UncheckedTypeException("call set method error.", e);
         }
         return t;
@@ -670,7 +671,7 @@ public abstract class Row {
 
     @Override
     public String toString() {
-        if (isEmpty()) return null;
+        if (isEmpty()) return "";
         StringJoiner joiner = new StringJoiner(" | ");
         // show row number
 //        joiner.add(String.valueOf(getRowNumber()));
diff --git a/src/main/java/org/ttzero/excel/util/ReflectUtil.java b/src/main/java/org/ttzero/excel/util/ReflectUtil.java
new file mode 100644
index 00000000..bdbd26bf
--- /dev/null
+++ b/src/main/java/org/ttzero/excel/util/ReflectUtil.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (c) 2019, guanquan.wang@yandex.com All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.ttzero.excel.util;
+
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.MethodDescriptor;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * Create by guanquan.wang at 2019-08-15 21:02
+ */
+public class ReflectUtil {
+    private ReflectUtil() {
+    }
+
+    /**
+     * List all declared fields that contains all supper class
+     *
+     * @param beanClass The bean class to be analyzed.
+     * @return all declared fields
+     */
+    public static Field[] listDeclaredFields(Class<?> beanClass) {
+        return listDeclaredFields(beanClass, Object.class);
+    }
+
+    /**
+     * List all declared fields that contains all supper class
+     *
+     * @param beanClass The bean class to be analyzed.
+     * @param stopClass The base class at which to stop the analysis.  Any
+     *                  methods/properties/events in the stopClass or in its base classes
+     *                  will be ignored in the analysis.
+     * @return all declared fields
+     */
+    public static Field[] listDeclaredFields(Class<?> beanClass, Class<?> stopClass) {
+        Field[] fields = beanClass.getDeclaredFields();
+        int i = fields.length, last = 0;
+        for (; (beanClass = beanClass.getSuperclass()) != stopClass; ) {
+            Field[] subFields = beanClass.getDeclaredFields();
+            if (subFields.length > 0) {
+                if (subFields.length > last) {
+                    Field[] tmp = new Field[fields.length + subFields.length];
+                    System.arraycopy(fields, 0, tmp, 0, i);
+                    fields = tmp;
+                    last = tmp.length - i;
+                }
+                System.arraycopy(subFields, 0, fields, i, subFields.length);
+                i += subFields.length;
+                last -= subFields.length;
+            }
+        }
+        return fields;
+    }
+
+    /**
+     * List all declared fields that contains all supper class
+     *
+     * @param beanClass The bean class to be analyzed.
+     * @param predicate A field filter
+     * @return all declared fields
+     */
+    public static Field[] listDeclaredFields(Class<?> beanClass, Predicate<Field> predicate) {
+        return listDeclaredFields(beanClass, Object.class, predicate);
+    }
+
+    /**
+     * List all declared fields that contains all supper class
+     *
+     * @param beanClass The bean class to be analyzed.
+     * @param stopClass The base class at which to stop the analysis.  Any
+     *                  methods/properties/events in the stopClass or in its base classes
+     *                  will be ignored in the analysis.
+     * @param predicate A field filter
+     * @return all declared fields
+     */
+    public static Field[] listDeclaredFields(Class<?> beanClass, Class<?> stopClass, Predicate<Field> predicate) {
+        Field[] fields = listDeclaredFields(beanClass, stopClass);
+
+        return fieldFilter(fields, predicate);
+    }
+
+    /**
+     * List all declared methods that contains all supper class
+     *
+     * @param beanClass The bean class to be analyzed.
+     * @return all declared method
+     */
+    public static Method[] listDeclaredMethods(Class<?> beanClass)
+        throws IntrospectionException {
+        return listDeclaredMethods(beanClass, Object.class);
+    }
+
+    /**
+     * List all declared methods that contains all supper class
+     *
+     * @param beanClass The bean class to be analyzed.
+     * @param predicate A method filter
+     * @return all declared method
+     */
+    public static Method[] listDeclaredMethods(Class<?> beanClass, Predicate<Method> predicate)
+        throws IntrospectionException {
+        Method[] methods = listDeclaredMethods(beanClass);
+
+        return methodFilter(methods, predicate);
+    }
+
+    /**
+     * List all declared methods that contains all supper class
+     *
+     * @param beanClass The bean class to be analyzed.
+     * @param stopClass The base class at which to stop the analysis.  Any
+     *                  methods/properties/events in the stopClass or in its base classes
+     *                  will be ignored in the analysis.
+     * @return all declared method
+     */
+    public static Method[] listDeclaredMethods(Class<?> beanClass, Class<?> stopClass)
+        throws IntrospectionException {
+        MethodDescriptor[] methodDescriptors = Introspector.getBeanInfo(beanClass, stopClass).getMethodDescriptors();
+        Method[] allMethods = beanClass.getMethods();
+        Method[] methods;
+        if (methodDescriptors.length > 0) {
+            methods = new Method[methodDescriptors.length];
+            for (int i = 0; i < methodDescriptors.length; i++) {
+                int index = indexOf(allMethods, methodDescriptors[i].getMethod());
+                methods[i] = index >= 0 ? allMethods[index] : methodDescriptors[i].getMethod();
+            }
+        } else methods = new Method[0];
+
+        return methods;
+    }
+
+    /**
+     * List all declared methods that contains all supper class
+     *
+     * @param beanClass The bean class to be analyzed.
+     * @param stopClass The base class at which to stop the analysis.  Any
+     *                  methods/properties/events in the stopClass or in its base classes
+     *                  will be ignored in the analysis.
+     * @param predicate A method filter
+     * @return all declared method
+     */
+    public static Method[] listDeclaredMethods(Class<?> beanClass, Class<?> stopClass, Predicate<Method> predicate)
+        throws IntrospectionException {
+        Method[] methods = listDeclaredMethods(beanClass, stopClass);
+
+        return methodFilter(methods, predicate);
+    }
+
+    /**
+     * List all declared read methods that contains all supper class
+     *
+     * @param beanClass The bean class to be analyzed.
+     * @return all declared method
+     */
+    public static Method[] listReadMethods(Class<?> beanClass) throws IntrospectionException {
+        return listReadMethods(beanClass, Object.class);
+    }
+
+    /**
+     * List all declared read methods that contains all supper class
+     *
+     * @param beanClass The bean class to be analyzed.
+     * @param stopClass The base class at which to stop the analysis.  Any
+     *                  methods/properties/events in the stopClass or in its base classes
+     *                  will be ignored in the analysis.
+     * @return all declared method
+     */
+    public static Method[] listReadMethods(Class<?> beanClass, Class<?> stopClass)
+        throws IntrospectionException {
+        Method[] methods = listDeclaredMethods(beanClass, stopClass);
+
+        int n = 0;
+        for ( int i = 0; i < methods.length; i++) {
+            Method method = methods[i];
+            if (method.getParameterCount() == 0) {
+                Class<?> returnType = method.getReturnType();
+                if (returnType != void.class && returnType != Void.class) {
+                    methods[n++] = method;
+                }
+            }
+        }
+
+        return n < methods.length ? Arrays.copyOf(methods, n) : methods;
+    }
+
+    /**
+     * List all declared read methods that contains all supper class
+     *
+     * @param beanClass The bean class to be analyzed.
+     * @param predicate A method filter
+     * @return all declared method
+     */
+    public static Method[] listReadMethods(Class<?> beanClass, Predicate<Method> predicate)
+        throws IntrospectionException {
+        Method[] methods = listReadMethods(beanClass);
+
+        return methodFilter(methods, predicate);
+    }
+
+    /**
+     * List all declared read methods that contains all supper class
+     *
+     * @param beanClass The bean class to be analyzed.
+     * @param stopClass The base class at which to stop the analysis.  Any
+     *                  methods/properties/events in the stopClass or in its base classes
+     *                  will be ignored in the analysis.
+     * @param predicate A method filter
+     * @return all declared method
+     */
+    public static Method[] listReadMethods(Class<?> beanClass, Class<?> stopClass, Predicate<Method> predicate)
+        throws IntrospectionException {
+        Method[] methods = listReadMethods(beanClass, stopClass);
+
+        return methodFilter(methods, predicate);
+    }
+
+    /**
+     * List all declared write methods that contains all supper class
+     *
+     * @param beanClass The bean class to be analyzed.
+     * @return all declared method
+     */
+    public static Method[] listWriteMethods(Class<?> beanClass) throws IntrospectionException {
+        return listWriteMethods(beanClass, Object.class);
+    }
+
+    /**
+     * List all declared write methods that contains all supper class
+     *
+     * @param beanClass The bean class to be analyzed.
+     * @param stopClass The base class at which to stop the analysis.  Any
+     *                  methods/properties/events in the stopClass or in its base classes
+     *                  will be ignored in the analysis.
+     * @return all declared method
+     */
+    public static Method[] listWriteMethods(Class<?> beanClass, Class<?> stopClass)
+        throws IntrospectionException {
+        Method[] methods = listDeclaredMethods(beanClass, stopClass);
+        Field[] fields = listDeclaredFields(beanClass, stopClass);
+        Set<String> tmp = new HashSet<>();
+        for (Field field : fields)
+            tmp.add("set" + field.getName().toLowerCase());
+
+        int n = 0;
+        for ( int i = 0; i < methods.length; i++) {
+            Method method = methods[i];
+            Class<?> returnType = method.getReturnType();
+            if (method.getParameterCount() == 1 && (returnType == void.class || returnType == Void.class)
+                && tmp.contains(method.getName().toLowerCase())) {
+                methods[n++] = method;
+            }
+        }
+
+        return n < methods.length ? Arrays.copyOf(methods, n) : methods;
+    }
+
+    /**
+     * List all declared methods that contains all supper class
+     *
+     * @param beanClass The bean class to be analyzed.
+     * @param predicate A method filter
+     * @return all declared method
+     */
+    public static Method[] listWriteMethods(Class<?> beanClass, Predicate<Method> predicate)
+        throws IntrospectionException {
+        Method[] methods = listWriteMethods(beanClass);
+
+        return methodFilter(methods, predicate);
+    }
+
+    /**
+     * List all declared methods that contains all supper class
+     *
+     * @param beanClass The bean class to be analyzed.
+     * @param stopClass The base class at which to stop the analysis.  Any
+     *                  methods/properties/events in the stopClass or in its base classes
+     *                  will be ignored in the analysis.
+     * @param predicate A method filter
+     * @return all declared method
+     */
+    public static Method[] listWriteMethods(Class<?> beanClass, Class<?> stopClass, Predicate<Method> predicate)
+        throws IntrospectionException {
+        Method[] methods = listWriteMethods(beanClass, stopClass);
+
+        return methodFilter(methods, predicate);
+    }
+
+    /**
+     * Found source method in methods array witch the source method
+     * equals it or the has a same method name and return-type and same parameters
+     *
+     * @param methods the array methods to be found
+     * @param source the source method
+     * @return the index in method array
+     */
+    public static int indexOf(Method[] methods, Method source) {
+        int i = 0;
+        for (Method method : methods) {
+            if (method == null) {
+                i++;
+                continue;
+            }
+            if (method.equals(source)) {
+                return i;
+            }
+            if (method.getName().equals(source.getName())
+                && method.getReturnType() == source.getReturnType()
+                && parameterDeepEquals(method.getParameters(), source.getParameters())) {
+                return i;
+            }
+            i++;
+        }
+        return -1;
+    }
+
+    public static int mapping(Method[] writeMethods, Map<String, Method> tmp
+        , PropertyDescriptor[] propertyDescriptors, Method[] mergedMethods) {
+        if (writeMethods == null) {
+            for (int i = 1; i < propertyDescriptors.length; i++) {
+                PropertyDescriptor pd = propertyDescriptors[i];
+                tmp.put(pd.getName(), mergedMethods[i]);
+            }
+        } else {
+            int i;
+            for (int j = 0; j < mergedMethods.length; j++) {
+                i = mergedMethods[j] != null ? indexOf(writeMethods, mergedMethods[j]) : -1;
+                if (i >= 0) {
+                    writeMethods[i] = null;
+                }
+                tmp.put(propertyDescriptors[j].getName(), mergedMethods[j]);
+            }
+
+            i = 0;
+            for (int j = 0; j < writeMethods.length; j++) {
+                if (writeMethods[j] != null) {
+                    writeMethods[i++] = writeMethods[j];
+                }
+            }
+            return i;
+        }
+        return 0;
+    }
+
+    // Do Filter
+    private static Method[] methodFilter(Method[] methods, Predicate<Method> predicate) {
+        int n = 0;
+        for (int i = 0; i < methods.length; i++) {
+            Method method = methods[i];
+            if (predicate.test(method)) {
+                if (i != n) methods[n] = method;
+                n++;
+            }
+        }
+
+        return n < methods.length ? Arrays.copyOf(methods, n) : methods;
+    }
+
+    private static Field[] fieldFilter(Field[] fields, Predicate<Field> predicate) {
+        int n = 0;
+        for (int i = 0; i < fields.length; i++) {
+            Field field = fields[i];
+            field.setAccessible(true);
+            if (predicate.test(field)) {
+                if (i != n) fields[n] = field;
+                n++;
+            }
+        }
+
+        return n < fields.length ? Arrays.copyOf(fields, n) : fields;
+    }
+
+    // Parameter deep compare
+    private static boolean parameterDeepEquals(Parameter[] aClass, Parameter[] bClass) {
+        boolean equals = aClass.length == bClass.length;
+        if (equals) {
+            for (int i = 0; i < aClass.length; i++) {
+                if (!(equals = aClass[i].equals(bClass[i])
+                    || aClass[i].getType() == bClass[i].getType())
+                    ) {
+                    break;
+                }
+            }
+        }
+        return equals;
+    }
+}


Test patch:
diff --git a/src/test/java/org/ttzero/excel/entity/ListMapSheetTest.java b/src/test/java/org/ttzero/excel/entity/ListMapSheetTest.java
index 226a2ef4..9aae2be4 100644
--- a/src/test/java/org/ttzero/excel/entity/ListMapSheetTest.java
+++ b/src/test/java/org/ttzero/excel/entity/ListMapSheetTest.java
@@ -202,8 +202,8 @@ public class ListMapSheetTest extends WorkbookTest {
             .setAutoSize(true)
             .addSheet(new ListMapSheet(createTestData(10)
                 , WaterMark.of(author)
-                , new Sheet.Column("ID", "id", int.class)
-                , new Sheet.Column("NAME", "name", String.class)))
+                , new Sheet.Column("ID", "id")
+                , new Sheet.Column("NAME", "name")))
             .writeTo(defaultTestPath);
     }
 
diff --git a/src/test/java/org/ttzero/excel/entity/ListObjectSheetTest.java b/src/test/java/org/ttzero/excel/entity/ListObjectSheetTest.java
index c0c27274..f75551ae 100644
--- a/src/test/java/org/ttzero/excel/entity/ListObjectSheetTest.java
+++ b/src/test/java/org/ttzero/excel/entity/ListObjectSheetTest.java
@@ -26,8 +26,12 @@ import org.ttzero.excel.entity.style.Styles;
 import org.ttzero.excel.processor.IntConversionProcessor;
 import org.ttzero.excel.processor.StyleProcessor;
 
-import java.awt.*;
+import java.awt.Color;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
 import java.io.IOException;
+import java.lang.reflect.Field;
 import java.math.BigDecimal;
 import java.sql.Time;
 import java.sql.Timestamp;
@@ -40,6 +44,7 @@ import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 
+import static org.ttzero.excel.Print.println;
 import static org.ttzero.excel.reader.ExcelReaderTest.testResourceRoot;
 
 /**
@@ -113,9 +118,9 @@ public class ListObjectSheetTest extends WorkbookTest{
         new Workbook("test int conversion", author)
             .watch(Print::println)
             .addSheet(Student.randomTestData()
-                , new Sheet.Column("学号", "id", int.class)
-                , new Sheet.Column("姓名", "name", String.class)
-                , new Sheet.Column("成绩", "score", int.class, n -> n < 60 ? "不及格" : n)
+                , new Sheet.Column("学号", "id")
+                , new Sheet.Column("姓名", "name")
+                , new Sheet.Column("成绩", "score", n -> n < 60 ? "不及格" : n)
             )
             .writeTo(defaultTestPath);
     }
@@ -124,9 +129,9 @@ public class ListObjectSheetTest extends WorkbookTest{
         new Workbook("object style processor", author)
             .watch(Print::println)
             .addSheet(Student.randomTestData()
-                , new Sheet.Column("学号", "id", int.class)
-                , new Sheet.Column("姓名", "name", String.class)
-                , new Sheet.Column("成绩", "score", int.class)
+                , new Sheet.Column("学号", "id")
+                , new Sheet.Column("姓名", "name")
+                , new Sheet.Column("成绩", "score")
                     .setStyleProcessor((o, style, sst) -> {
                         if ((int)o < 60) {
                             style = Styles.clearFill(style)
@@ -142,9 +147,9 @@ public class ListObjectSheetTest extends WorkbookTest{
         new Workbook("object style and style processor", author)
             .watch(Print::println)
             .addSheet(Student.randomTestData()
-                , new Sheet.Column("学号", "id", int.class)
-                , new Sheet.Column("姓名", "name", String.class)
-                , new Sheet.Column("成绩", "score", int.class, n -> n < 60 ? "不及格" : n)
+                , new Sheet.Column("学号", "id")
+                , new Sheet.Column("姓名", "name")
+                , new Sheet.Column("成绩", "score", n -> n < 60 ? "不及格" : n)
                     .setStyleProcessor((o, style, sst) -> {
                         if ((int)o < 60) {
                             style = Styles.clearFill(style)
@@ -209,8 +214,8 @@ public class ListObjectSheetTest extends WorkbookTest{
             .watch(Print::println)
             .setAutoSize(true)
             .addSheet(new ListSheet<Item>("Item"
-                , new Sheet.Column("ID", "id", int.class)
-                , new Sheet.Column("NAME", "name", String.class))
+                , new Sheet.Column("ID", "id")
+                , new Sheet.Column("NAME", "name"))
                 .setData(Item.randomTestData(10)))
             .writeTo(defaultTestPath);
     }
@@ -220,8 +225,8 @@ public class ListObjectSheetTest extends WorkbookTest{
             .watch(Print::println)
             .setAutoSize(true)
             .addSheet(new ListSheet<Item>("Item", WaterMark.of(author)
-                , new Sheet.Column("ID", "id", int.class)
-                , new Sheet.Column("NAME", "name", String.class))
+                , new Sheet.Column("ID", "id")
+                , new Sheet.Column("NAME", "name"))
                 .setData(Item.randomTestData(10)))
             .writeTo(defaultTestPath);
     }
@@ -247,8 +252,8 @@ public class ListObjectSheetTest extends WorkbookTest{
             .watch(Print::println)
             .setAutoSize(true)
             .addSheet(new ListSheet<>(Item.randomTestData(10)
-                , new Sheet.Column("ID", "id", int.class)
-                , new Sheet.Column("NAME", "name", String.class)))
+                , new Sheet.Column("ID", "id")
+                , new Sheet.Column("NAME", "name")))
             .writeTo(defaultTestPath);
     }
 
@@ -257,8 +262,8 @@ public class ListObjectSheetTest extends WorkbookTest{
             .watch(Print::println)
             .setAutoSize(true)
             .addSheet(new ListSheet<>("ITEM", Item.randomTestData(10)
-                , new Sheet.Column("ID", "id", int.class)
-                , new Sheet.Column("NAME", "name", String.class)))
+                , new Sheet.Column("ID", "id")
+                , new Sheet.Column("NAME", "name")))
             .writeTo(defaultTestPath);
     }
 
@@ -268,8 +273,8 @@ public class ListObjectSheetTest extends WorkbookTest{
             .setAutoSize(true)
             .addSheet(new ListSheet<>(Item.randomTestData(10)
                 , WaterMark.of(author)
-                , new Sheet.Column("ID", "id", int.class)
-                , new Sheet.Column("NAME", "name", String.class)))
+                , new Sheet.Column("ID", "id")
+                , new Sheet.Column("NAME", "name")))
             .writeTo(defaultTestPath);
     }
 
@@ -280,8 +285,8 @@ public class ListObjectSheetTest extends WorkbookTest{
             .addSheet(new ListSheet<>("ITEM"
                 , Item.randomTestData(10)
                 , WaterMark.of(author)
-                , new Sheet.Column("ID", "id", int.class)
-                , new Sheet.Column("NAME", "name", String.class)))
+                , new Sheet.Column("ID", "id")
+                , new Sheet.Column("NAME", "name")))
             .writeTo(defaultTestPath);
     }
 
@@ -316,9 +321,9 @@ public class ListObjectSheetTest extends WorkbookTest{
     public void testStyleConversion1() throws IOException {
         new Workbook("object style processor1", "guanquan.wang")
             .addSheet(new ListSheet<>("期末成绩", Student.randomTestData()
-                    , new Sheet.Column("学号", "id", int.class)
-                    , new Sheet.Column("姓名", "name", String.class)
-                    , new Sheet.Column("成绩", "score", int.class, conversion)
+                    , new Sheet.Column("学号", "id")
+                    , new Sheet.Column("姓名", "name")
+                    , new Sheet.Column("成绩", "score", conversion)
                     .setStyleProcessor(sp)
                 )
             )
@@ -329,10 +334,67 @@ public class ListObjectSheetTest extends WorkbookTest{
         new Workbook("test null value", author)
             .watch(Print::println)
             .setAutoSize(true)
-            .addSheet(new ListSheet<>("EXT-ITEM", ExtItem.randomTestData(10)
-                , new Sheet.Column("学号", "id", Integer.class, i -> i == 0 ? -1 : i)
-                , new Sheet.Column("姓名", "name", String.class)
-                , new Sheet.Column("妮称", "nice", String.class))
+            .addSheet(new ListSheet<>("EXT-ITEM", ExtItem.randomTestData(10)))
+            .writeTo(defaultTestPath);
+    }
+
+    @Test public void testReflect() throws IntrospectionException, IllegalAccessException {
+        PropertyDescriptor[] array = Introspector.getBeanInfo(ExtItem.class).getPropertyDescriptors();
+        for (PropertyDescriptor pd : array) {
+            println(pd);
+        }
+        ExtItem item = new ExtItem(1, "guanquan.wang");
+        item.nice = "colvin";
+
+        Field[] fields = item.getClass().getDeclaredFields();
+        for (Field field : fields) {
+            field.setAccessible(true);
+            println(field + ": " + field.get(item));
+        }
+    }
+
+    @Test public void testFieldUnDeclare() throws IOException {
+        try {
+            new Workbook("field un-declare", author)
+                .addSheet(new ListSheet<>("期末成绩", Student.randomTestData()
+                        , new Sheet.Column("学号", "id")
+                        , new Sheet.Column("姓名", "name")
+                        , new Sheet.Column("成绩", "sore") // un-declare field
+                    )
+                )
+                .writeTo(defaultTestPath);
+        } catch (ExcelWriteException e) {
+            assert true;
+        }
+    }
+
+    @Test public void testResetMethod() throws IOException {
+        new Workbook("重写期末成绩", author)
+            .addSheet(new ListSheet<Student>("重写期末成绩", Collections.singletonList(new Student(9527, author, 0) {
+                    @Override
+                    public int getScore() {
+                        return 100;
+                    }
+                }))
+            )
+            .writeTo(defaultTestPath);
+    }
+
+    @Test public void testMethodAnnotation() throws IOException {
+        new Workbook("重写方法注解", author)
+            .addSheet(new ListSheet<Student>("重写方法注解", Collections.singletonList(new Student(9527, author, 0) {
+                @Override
+                @ExcelColumn("ID")
+                public int getId() {
+                    return super.id;
+                }
+
+                @Override
+                @ExcelColumn("SCORE")
+                public int getScore() {
+                    return 97;
+                }
+            }))
             )
             .writeTo(defaultTestPath);
     }
@@ -346,6 +408,14 @@ public class ListObjectSheetTest extends WorkbookTest{
             this.name = name;
         }
 
+        public int getId() {
+            return id;
+        }
+
+        public String getName() {
+            return name;
+        }
+
         public static List<Item> randomTestData(int n) {
             List<Item> list = new ArrayList<>(n);
             for (int i = 0; i < n; i++) {
@@ -405,6 +475,66 @@ public class ListObjectSheetTest extends WorkbookTest{
             int size = random.nextInt(100) + 1;
             return randomTestData(size);
         }
+
+        public boolean isBv() {
+            return bv;
+        }
+
+        public char getCv() {
+            return cv;
+        }
+
+        public short getSv() {
+            return sv;
+        }
+
+        public int getNv() {
+            return nv;
+        }
+
+        public long getLv() {
+            return lv;
+        }
+
+        public float getFv() {
+            return fv;
+        }
+
+        public double getDv() {
+            return dv;
+        }
+
+        public String getS() {
+            return s;
+        }
+
+        public BigDecimal getMv() {
+            return mv;
+        }
+
+        public Date getAv() {
+            return av;
+        }
+
+        public Timestamp getIv() {
+            return iv;
+        }
+
+        public Time getTv() {
+            return tv;
+        }
+
+        public LocalDate getLdv() {
+            return ldv;
+        }
+
+        public LocalDateTime getLdtv() {
+            return ldtv;
+        }
+
+        public LocalTime getLtv() {
+            return ltv;
+        }
     }
 
     /**
@@ -423,6 +553,31 @@ public class ListObjectSheetTest extends WorkbookTest{
             this.name = name;
             this.score = score;
         }
+
+        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 int getScore() {
+            return score;
+        }
+
+        public void setScore(int score) {
+            this.score = score;
+        }
+
         public static List<Student> randomTestData(int pageNo, int limit) {
             List<Student> list = new ArrayList<>(limit);
             for (int i = pageNo * limit, n = i + limit; i < n; i++) {
@@ -440,6 +595,12 @@ public class ListObjectSheetTest extends WorkbookTest{
             int n = random.nextInt(100) + 1;
             return randomTestData(n);
         }
+
+        @Override
+        @ExcelColumn
+        public String toString() {
+            return "id: " + id + ", name: " + name + ", score: " + score;
+        }
     }
 
     public static class BoxAllType {
@@ -487,21 +648,83 @@ public class ListObjectSheetTest extends WorkbookTest{
             int size = random.nextInt(100) + 1;
             return randomTestData(size);
         }
+
+        public Boolean getBv() {
+            return bv;
+        }
+
+        public Character getCv() {
+            return cv;
+        }
+
+        public Short getSv() {
+            return sv;
+        }
+
+        public Integer getNv() {
+            return nv;
+        }
+
+        public Long getLv() {
+            return lv;
+        }
+
+        public Float getFv() {
+            return fv;
+        }
+
+        public Double getDv() {
+            return dv;
+        }
+
+        public String getS() {
+            return s;
+        }
+
+        public BigDecimal getMv() {
+            return mv;
+        }
+
+        public Date getAv() {
+            return av;
+        }
+
+        public Timestamp getIv() {
+            return iv;
+        }
+
+        public Time getTv() {
+            return tv;
+        }
+
+        public LocalDate getLdv() {
+            return ldv;
+        }
+
+        public LocalDateTime getLdtv() {
+            return ldtv;
+        }
+
+        public LocalTime getLtv() {
+            return ltv;
+        }
     }
 
-    public static class ExtItem {
-        private Integer id;
-        private String name;
+    public static class ExtItem extends Item {
         private String nice;
 
-        ExtItem(String name) {
-            this.name = name;
+        ExtItem(int id, String name) {
+            super(id, name);
+        }
+
+        public String getNice() {
+            return nice;
         }
 
-        public static List<ExtItem> randomTestData(int n) {
-            List<ExtItem> list = new ArrayList<>(n);
+        public static List<Item> randomTestData(int n) {
+            List<Item> list = new ArrayList<>(n);
             for (int i = 0; i < n; i++) {
-                list.add(new ExtItem(getRandomString()));
+                list.add(new ExtItem(i,  getRandomString()));
             }
             return list;
         }
diff --git a/src/test/java/org/ttzero/excel/reader/ExcelReaderTest.java b/src/test/java/org/ttzero/excel/reader/ExcelReaderTest.java
index 85c18551..ad268cc1 100644
--- a/src/test/java/org/ttzero/excel/reader/ExcelReaderTest.java
+++ b/src/test/java/org/ttzero/excel/reader/ExcelReaderTest.java
@@ -18,13 +18,17 @@ package org.ttzero.excel.reader;
 
 import org.junit.Test;
 import org.ttzero.excel.Print;
+import org.ttzero.excel.annotation.ExcelColumn;
+import org.ttzero.excel.annotation.IgnoreExport;
 import org.ttzero.excel.manager.ExcelType;
+import org.ttzero.excel.util.DateUtil;
 import org.ttzero.excel.util.FileUtil;
 
 import java.io.IOException;
 import java.net.URL;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Date;
 import java.util.Iterator;
 
 import static org.ttzero.excel.Print.println;
@@ -121,4 +125,213 @@ public class ExcelReaderTest {
             e.printStackTrace();
         }
     }
+
+    @Test public void testToStandardObject() {
+        try (ExcelReader reader = ExcelReader.read(testResourceRoot().resolve("1.xlsx"))) {
+            reader.sheets().flatMap(Sheet::dataRows).map(row -> row.too(StandardEntry.class)).forEach(Print::println);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Test public void testToObject() {
+        try (ExcelReader reader = ExcelReader.read(testResourceRoot().resolve("1.xlsx"))) {
+            reader.sheets().flatMap(Sheet::dataRows).map(row -> row.too(Entry.class)).forEach(Print::println);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Test public void testToAnnotationObject() {
+        try (ExcelReader reader = ExcelReader.read(testResourceRoot().resolve("1.xlsx"))) {
+            reader.sheets().flatMap(Sheet::dataRows).map(row -> row.too(AnnotationEntry.class)).forEach(Print::println);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static class Entry {
+        @ExcelColumn("渠道ID")
+        private Integer channelId;
+        @ExcelColumn(value = "游戏", share = true)
+        private String pro;
+        @ExcelColumn
+        private String account;
+        @ExcelColumn("注册时间")
+        private java.util.Date registered;
+        @ExcelColumn("是否满30级")
+        private boolean up30;
+        @IgnoreExport("敏感信息不导出")
+        private int id; // not export
+        private String address;
+        @ExcelColumn("VIP")
+        private char c;
+
+        private boolean vip;
+
+        public boolean isUp30() {
+            return up30;
+        }
+
+        /**
+         * Convert game name to code
+         *
+         * @param pro the game nice name
+         */
+        public void setPro(String pro) {
+            // "LOL", "WOW", "极品飞车", "守望先锋", "怪物世界"
+            String code;
+            switch (pro) {
+                case "LOL"   : code = "1"; break;
+                case "WOW"   : code = "2"; break;
+                case "极品飞车": code = "3"; break;
+                case "守望先锋": code = "4"; break;
+                case "怪物世界": code = "5"; break;
+                default: code = "0";
+            }
+            this.pro = code;
+        }
+
+        public void setC(char c) {
+            this.c = c;
+            this.vip = c == 'A';
+        }
+
+        public boolean isVip() {
+            return vip;
+        }
+
+        @Override
+        public String toString() {
+            return channelId + " | "
+                + pro + " | "
+                + account + " | "
+                + (registered != null ? DateUtil.toDateString(registered) : null) + " | "
+                + up30 + " | "
+                + c + " | "
+                + isVip()
+                ;
+        }
+    }
+
+    public static class StandardEntry {
+        private Integer channelId;
+        private String pro;
+        private String account;
+        private java.util.Date registered;
+        private boolean up30;
+        private int id;
+        private String address;
+        private char c;
+
+        private boolean vip;
+
+        public void setChannelId(Integer channelId) {
+            this.channelId = channelId;
+        }
+
+        public void setPro(String pro) {
+            this.pro = pro;
+        }
+
+        public void setAccount(String account) {
+            this.account = account;
+        }
+
+        public void setRegistered(Date registered) {
+            this.registered = registered;
+        }
+
+        public void setUp30(boolean up30) {
+            this.up30 = up30;
+        }
+
+        public void setId(int id) {
+            this.id = id;
+        }
+
+        public void setAddress(String address) {
+            this.address = address;
+        }
+
+        public void setC(char c) {
+            this.c = c;
+            this.vip = c == 'A';
+        }
+
+        @Override
+        public String toString() {
+            return channelId + " | "
+                + pro + " | "
+                + account + " | "
+                + (registered != null ? DateUtil.toDateString(registered) : null) + " | "
+                + up30 + " | "
+                + c + " | "
+                + vip
+                ;
+        }
+    }
+
+    public static class AnnotationEntry {
+        private Integer channelId;
+        private String pro;
+        private String account;
+        private java.util.Date registered;
+        private boolean up30;
+        private int id;
+        private String address;
+        private char c;
+
+        private boolean vip;
+
+        @ExcelColumn("渠道ID")
+        public void setChannelId(Integer channelId) {
+            this.channelId = channelId;
+        }
+
+        @ExcelColumn(value = "游戏")
+        public void setPro(String pro) {
+            this.pro = pro;
+        }
+
+        public void setAccount(String account) {
+            this.account = account;
+        }
+
+        @ExcelColumn("注册时间")
+        public void setRegistered(Date registered) {
+            this.registered = registered;
+        }
+
+        @ExcelColumn("是否满30级")
+        public void setUp30(boolean up30) {
+            this.up30 = up30;
+        }
+
+        public void setId(int id) {
+            this.id = id;
+        }
+
+        public void setAddress(String address) {
+            this.address = address;
+        }
+
+        @ExcelColumn("VIP")
+        public void setC(char c) {
+            this.c = c;
+            this.vip = c == 'A';
+        }
+
+        @Override
+        public String toString() {
+            return channelId + " | "
+                + pro + " | "
+                + account + " | "
+                + (registered != null ? DateUtil.toDateString(registered) : null) + " | "
+                + up30 + " | "
+                + c + " | "
+                + vip
+                ;
+        }
+    }
 }
diff --git a/src/test/java/org/ttzero/excel/util/ReflectUtilTest.java b/src/test/java/org/ttzero/excel/util/ReflectUtilTest.java
new file mode 100644
index 00000000..32dc17be
--- /dev/null
+++ b/src/test/java/org/ttzero/excel/util/ReflectUtilTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 2019, guanquan.wang@yandex.com All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.ttzero.excel.util;
+
+import org.junit.Test;
+import org.ttzero.excel.annotation.ExcelColumn;
+import org.ttzero.excel.annotation.IgnoreExport;
+import org.ttzero.excel.annotation.IgnoreImport;
+
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.MethodDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+import static org.ttzero.excel.Print.println;
+import static org.ttzero.excel.util.ReflectUtil.listReadMethods;
+
+/**
+ * Create by guanquan.wang at 2019-08-15 21:46
+ */
+public class ReflectUtilTest {
+    @Test public void testListDeclaredFields() {
+        Field[] fields = ReflectUtil.listDeclaredFields(A.class);
+
+        assert fields.length == 1;
+        assert fields[0].getName().equals("a");
+    }
+
+    @Test public void testListDeclaredFields2() {
+        Field[] fields = ReflectUtil.listDeclaredFields(B.class);
+
+        assert fields.length == 1;
+        assert fields[0].getName().equals("a");
+    }
+
+    @Test public void testListDeclaredFields3() {
+        Field[] fields = ReflectUtil.listDeclaredFields(C.class);
+
+        assert fields.length == 2;
+        assert fields[0].getName().equals("c");
+        assert fields[1].getName().equals("a");
+    }
+
+    @Test public void testListDeclaredFields4() {
+        Field[] fields = ReflectUtil.listDeclaredFields(C.class, A.class);
+
+        assert fields.length == 1;
+        assert fields[0].getName().equals("c");
+    }
+
+    @Test public void testListDeclaredFields5() {
+        Field[] fields = ReflectUtil.listDeclaredFields(C.class
+            , field -> field.getAnnotation(ExcelColumn.class) != null);
+
+        assert fields.length == 1;
+        assert fields[0].getName().equals("a");
+    }
+
+    @Test public void testListDeclaredMethod1() throws IntrospectionException {
+        Method[] methods = ReflectUtil.listDeclaredMethods(C.class);
+
+        assert methods.length == 6;
+    }
+
+    @Test public void testListDeclaredMethod2() throws IntrospectionException {
+        Method[] methods = ReflectUtil.listDeclaredMethods(C.class, B.class);
+
+        assert methods.length == 4;
+    }
+
+    @Test public void testListDeclaredMethod3() throws IntrospectionException {
+        Method[] methods = ReflectUtil.listDeclaredMethods(C.class, method -> method.getName().startsWith("set"));
+
+        assert methods.length == 2;
+    }
+
+    @Test public void testListDeclaredMethod4() throws IntrospectionException {
+        Method[] methods = ReflectUtil.listDeclaredMethods(C.class, B.class, method -> method.getName().startsWith("set"));
+
+        assert methods.length == 1;
+    }
+
+    @Test public void testListReadMethod1() throws IntrospectionException {
+        Method[] methods = ReflectUtil.listReadMethods(C.class);
+
+        assert methods.length == 3;
+    }
+
+    @Test public void testListReadMethod2() throws IntrospectionException {
+        Method[] methods = ReflectUtil.listReadMethods(C.class, A.class);
+
+        assert methods.length == 2;
+    }
+
+    @Test public void testListReadMethod3() throws IntrospectionException {
+        Method[] methods = ReflectUtil.listReadMethods(C.class
+            , method -> method.getAnnotation(ExcelColumn.class) != null);
+
+        assert methods.length == 1;
+    }
+
+    @Test public void testListReadMethod4() throws IntrospectionException {
+        Method[] methods = ReflectUtil.listReadMethods(C.class, A.class
+            , method -> method.getAnnotation(IgnoreExport.class) != null);
+
+        assert methods.length == 0;
+    }
+
+    @Test public void testListWriteMethod1() throws IntrospectionException {
+        Method[] methods = ReflectUtil.listWriteMethods(C.class);
+
+        assert methods.length == 2;
+    }
+
+    @Test public void testListWriteMethod2() throws IntrospectionException {
+        Method[] methods = ReflectUtil.listWriteMethods(C.class, A.class);
+
+        assert methods.length == 1;
+    }
+
+    @Test public void testListWriteMethod3() throws IntrospectionException {
+        Method[] methods = ReflectUtil.listWriteMethods(C.class
+            , method -> method.getAnnotation(ExcelColumn.class) != null);
+
+        assert methods.length == 0;
+    }
+
+    @Test public void testListWriteMethod4() throws IntrospectionException {
+        Method[] methods = ReflectUtil.listWriteMethods(C.class
+            , method -> method.getAnnotation(IgnoreImport.class) != null);
+
+        for (Method method : methods)
+            println(method);
+        assert methods.length == 1;
+    }
+
+    @Test public void test() throws IntrospectionException {
+        MethodDescriptor[] methods = Introspector.getBeanInfo(C.class, Object.class).getMethodDescriptors();
+        for (MethodDescriptor method : methods) {
+            println(method);
+            Class<?> returnType = method.getMethod().getReturnType();
+            println(returnType);
+        }
+    }
+
+    @Test public void testRewriteMethod() throws IntrospectionException, InvocationTargetException, IllegalAccessException {
+        C c = new C() {
+            @Override
+            @IgnoreImport
+            @ExcelColumn("name")
+            public void setC(long c) {
+                super.c = c;
+            }
+
+            @Override
+            @IgnoreExport("CODE")
+            @ExcelColumn("CODE")
+            public long getC() {
+                return 9527L;
+            }
+        };
+
+        Method[] methods = listReadMethods(c.getClass());
+        for (Method method : methods) {
+            println(method);
+            println(Arrays.toString(method.getAnnotations()));
+        }
+    }
+
+    public static class A {
+        @ExcelColumn
+        private int a;
+
+        public int getA() {
+            return a;
+        }
+
+        @IgnoreImport
+        public void setA(int a) {
+            this.a = a;
+        }
+    }
+
+    static class B extends A {
+        @Override
+        public String toString() {
+            return "B <- A";
+        }
+
+        public int doIt(int a) {
+            return 0;
+        }
+    }
+
+    public static class C extends B {
+        private long c;
+
+        @ExcelColumn
+        public long getC() {
+            return c;
+        }
+
+        public void setC(long c) {
+            this.c = c;
+        }
+
+        @Override
+        public int doIt(int a) {
+            return 0;
+        }
+    }
+}