问题背景
有如下的一个类,属性有很多,每个字段按照出现在 excel(xsl) 中的固定顺序解析成不同的数据类型,如字符串、BigDecimal、Integer 等
@NotBlankprivate String title; // 商品标题@NotBlankprivate String l1Category; // 一级品类...
另外当某一条数据没有校验通过时,需要提供详细的哪一行哪一列的数据有问题,如下所示的信息
【R3 : 第 3 行, 第 18 列】:成本价 不是一个有效的人民币数字
此条信息中有两个点需要处理:
- 推导出某个字段在第几列:数据行信息,在解析文件的时候能获取到,但是列信息,有可能获取不到,比如:要求 5 个字段,但是只填写了第一个字段,后面的都是空的,所以只能根据定好的字段顺序,去推导出来,在第几列;
- 将第 n 列中的数字 n 转换为 excel 中的字母列
比如:规定出现在文件中的顺序为 title,name,....
想要找到该字段是第几列最好的方式是事先定义好,它的固定顺序, 方式有很多种,第一种能想到的就是如下方式
public static final Map<String, Integer> fieldIndexs = new HashMap<>();static {fieldIndexs.put("title", 0);fieldIndexs.put("name", 1);....}
在使用的地方也是这样手动的写上固定的字符串,但是这样有一个缺点:当字段很多,而且有可能字段名还没有调整好的时候,或则新增字段时,需要修改很多地方的字符串
你可能会说,可以把所有的字段都写成常量,这也有一个问题,需要同步数据对象和常量字符串。
笔者在这里发现 tkmapper 中有一个工具类,可以使用 jdk8 的方法引用 + 反射获取到方法的名称,下面来看看是如何实现的
解决方案
推导出某个字段在第几列
先来看看调用方法
public static String extractFieldName(Fn<BatchImportParseProductRow, Object> fn) {return Reflections.fnToFieldName(fn);}public static void main(String[] args) {// titlefinal String s = extractFieldName(BatchImportParseProductRow::getTitle);
BatchImportParseProductRow 是你的数据对象,通过方法引用 BatchImportParseProductRow::getTitle 传递给了工具类,下面来看看这个工具类
package tk.mybatis.mapper.weekend.reflection;import tk.mybatis.mapper.weekend.Fn;import java.beans.Introspector;import java.lang.invoke.SerializedLambda;import java.lang.reflect.Method;import java.util.regex.Pattern;/*** @author Frank*/public class Reflections {private static final Pattern GET_PATTERN = Pattern.compile("^get[A-Z].*");private static final Pattern IS_PATTERN = Pattern.compile("^is[A-Z].*");private Reflections() {}public static String fnToFieldName(Fn fn) {try {Method method = fn.getClass().getDeclaredMethod("writeReplace");method.setAccessible(Boolean.TRUE);SerializedLambda serializedLambda = (SerializedLambda) method.invoke(fn);String getter = serializedLambda.getImplMethodName();if (GET_PATTERN.matcher(getter).matches()) {getter = getter.substring(3);} else if (IS_PATTERN.matcher(getter).matches()) {getter = getter.substring(2);}// 这个类是 jdk 自带的将首字母变成小写的方法// java.beans.Introspector#decapitalizereturn Introspector.decapitalize(getter);} catch (ReflectiveOperationException e) {throw new ReflectionOperationException(e);}}}
这里最主要的是这个 writeReplace method 对象,我猜想应该是实现了 Serializable 接口才会出现这个方法,Fn 的定义如下
package tk.mybatis.mapper.weekend;import java.io.Serializable;import java.util.function.Function;/*** @author Frank*/public interface Fn<T, R> extends Function<T, R>, Serializable {}
下面利用这个工具类来实现我们的需求:先定义两个方法,通过方法引用获取字段名称、通过方法引用获取出现在文件中的固定顺序
/*** 获取字段的位置,BatchImportParseProductRow 为你的数据对象** @param fn* @return*/public static int position(Fn<BatchImportParseProductRow, Object> fn) {return fieldIndexs.get(extractFieldName(fn));}public static String extractFieldName(Fn<BatchImportParseProductRow, Object> fn) {return Reflections.fnToFieldName(fn);}
再通过 map 形式定义好字段出现的顺序
public static final Map<String, Integer> fieldIndexs = new HashMap<>();static {fieldIndexs.put(extractFieldName(BatchImportParseProductRow::getTitle), 0);fieldIndexs.put(extractFieldName(BatchImportParseProductRow::getL1Category), 1);fieldIndexs.put(extractFieldName(BatchImportParseProductRow::getL2Category), 2);
以后修改了这个数据对象的的名称,通过 IDEA 重构方式,可以一处修改,多处生效,使用也比较方便
数字与 Excel 的列字母互转
这个功能是百度找的一段代码,这里直接贴出来
/*** Excel 列的下标 和 字母互转** @author Stephen.Huang* @version 2015-7-8*/public class ExcelColumnUtil {public static void main(String[] args) {String colstr = "AA";int colIndex = excelColStrToNum(colstr, colstr.length());System.out.println("'" + colstr + "' column index of " + colIndex);colIndex = 26;colstr = excelColIndexToStr(colIndex);System.out.println(colIndex + " column in excel of " + colstr);colstr = "AAAA";colIndex = excelColStrToNum(colstr, colstr.length());System.out.println("'" + colstr + "' column index of " + colIndex);colIndex = 466948;colstr = excelColIndexToStr(colIndex);System.out.println(colIndex + " column in excel of " + colstr);}public static int excelColStrToNum(String colStr) {return excelColStrToNum(colStr, colStr.length());}/*** 列字母转数字* <pre>** 注意:Excel column index 从 1 开始** </pre>** @param colStr* @param length* @return*/public static int excelColStrToNum(String colStr, int length) {int num = 0;int result = 0;for (int i = 0; i < length; i++) {char ch = colStr.charAt(length - i - 1);num = (int) (ch - 'A' + 1);num *= Math.pow(26, i);result += num;}return result;}/*** 将列转成字母** @param columnIndex 注意:Excel column index 从 1 开始* @return*/public static String excelColIndexToStr(int columnIndex) {if (columnIndex <= 0) {return null;}String columnStr = "";columnIndex--;do {if (columnStr.length() > 0) {columnIndex--;}columnStr = ((char) (columnIndex % 26 + (int) 'A')) + columnStr;columnIndex = (int) ((columnIndex - columnIndex % 26) / 26);} while (columnIndex > 0);return columnStr;}}
