CODE TASTE —— CODE REUSE

84 阅读3分钟

CODE REUSE

即 -> 代码复用性

背景与技术点

背景:写导入excel校验功能,通用性的非空校验方法(当然如果要逐个个性化校验另说)。
技术点:反射泛型的应用与解析

原代码

/**  
* 校验是否存在空数据  
* @param x  
*/  
private void validateBlank(StoreBrandRetentionImportExcelVO x) {  
    if (StringUtils.isBlank(x.getStoreCode())) {  
        throw exception(STORE_CODE_IMPORT_IS_EMPTY);  
    }  
    if (StringUtils.isBlank(x.getBrand())) {  
        throw exception(BRAND_IMPORT_IS_EMPTY);  
    }  
    if (StringUtils.isBlank(x.getDistributionGrade())) {  
        throw exception(DISTRIBUTION_GRADE_IMPORT_IS_EMPTY);  
    }  
    if (StringUtils.isBlank(x.getStandardRetention())) {  
        throw exception(STANDARD_RETENTION_IMPORT_IS_EMPTY);  
    }  
}

优化后

/**
 * Excel 工具类
 *
 * @author zhangsheng
 */
public class ExcelUtils {

    /**
     * 导入Excel 通用校验方法
     * @param list
     * @return
     * @param <T>
     */
    public static <T> CommonImportRespVO getCommonImportRespVO(List<T> list) {
        if (CollUtil.isEmpty(list)) {
            throw exception(IMPORT_LIST_IS_EMPTY);
        }
        CommonImportRespVO respVO = CommonImportRespVO.builder().createSize(0)
                .updateSize(0).failureObjects(new TreeSet<>()).build();
        // 校验
        for (int i = 0; i < list.size(); i++) {
            try {
                validateBlank(list.get(i));
            } catch (ServiceException ex) {
                // 第n行,校验失败
                respVO.getFailureObjects().add(String.format("第%s行,校验失败:%s", i + 1, ex.getMessage()));
            }
        }
        return respVO;
    }

   /**
     * 校验操作
     *
     * @param x
     */
    private static <T> void validateProcess(T x) {
        Class<?> clazz = x.getClass();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Object value;
            try {
                value = field.get(x);
            } catch (Exception e) {
                throw exception(IMPORT_ERROR, e.getMessage());
            }
            if (ImportExcelFieldConstants.BRAND.equals(field.getName())) {// 判断字段名是否为"brand"
                if (value instanceof String && !ExcelCheckEnum.BRAND.getName().contains(value.toString())) {
                    throw exception(BRAND_DATA_IS_ERROR,value);
                }
            }else{// 非空判断
                validateBlank(value);
            }

        }
    }

    /**
     * 校验是否为空
     *
     * @param value
     */
    private static void validateBlank(Object value) {
        if (value instanceof String) {
            if (StringUtils.isBlank(value.toString())) {
                throw exception(IMPORT_DATA_IS_EMPTY);
            }
        } else if (Objects.isNull(value)) {
            throw exception(IMPORT_DATA_IS_EMPTY);
        }
    }
    
}

/**
 * @author zhangsheng
 */
@Getter
@AllArgsConstructor
public enum ExcelCheckEnum implements IntArrayValuable {

    // 这里写成枚举,可以改成 -> 策略模式+数据库查询+缓存 -> 这边暂时没必要
    BRAND(0, "男装 女装 童装 鞋子"),
    GENDER(1, "男 女");

    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ExcelCheckEnum::getCode).toArray();

    /**
     * 值
     */
    private final Integer code;
    /**
     * 名称
     */
    private final String name;

    @Override
    public int[] array() {
        return ARRAYS;
    }

}

反射

获取 Class<?> 的三种方式

// 伪代码
Class<?> clazz = Class.forName(className);
Class<?> clazz = instance.getClass();
类名.class;

code

/**
 * 上文使用Class<?> clazz = Class.forName(className)的方式
 * 比较一下Class.forName(className)的方式 -> 不满足我的需求
 * @param className
 */
public void getClassByName(String className) {
    try {
        // 使用 Class.forName() 方法来获取 Class 对象,参数是类的完整限定名
        Class<?> clazz = Class.forName(className);

        // 打印类的名称
        System.out.println("Class Name: " + clazz.getName());

        // 打印类的所有方法名
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            System.out.println("Method Name: " + method.getName());
        }

        // 打印类的所有字段名
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            System.out.println("Field Name: " + field.getName());
        }

    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}


泛型

在编译期不确定参数类型,在运行期确定。方法签名的时候标记为泛型,类型根据传参来确认。

  1. 是泛型的声明,在类中跟在类名之后,在方法上在访问修饰符后,返回值前。
//demo1(类)
public interface List<E> extends Collection<E> {//...}
//demo2(方法)
private <T> void validateBlank(T x) {//...}
  1. T是对于泛型的使用,常见于传参、返回值等。
//同上
private <T> void validateBlank(T x) {//...}
  1. <?>是无界通配符 -> 有界通配符<? extends 类型>和<? super 类型>

无界通配符注意点:

在使用 <?> 通配符类型时,确实无法添加具体类型的元素到集合中,只能进行与类型无关的操作,例如遍历或删除元素。这样可以确保泛型的类型安全性,避免出现类型错误或转换异常。

// 无界通配符的demo
// demo1(正面)
List<?> list = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(2);
list = integers; // 可以将具体类型集合赋值给通配符类型集合
for (Object item : list) {
    System.out.println(item); // 可以遍历集合并打印元素
}

// demo2(反面)
List<?> list = new ArrayList<>();
list.add(1); // 运行时异常:UnsupportedOperationException

有界通配符注意点:

与通配符 <?> 不同,<? super 类型> 用于界定泛型下界,因此我们可以向这种集合中添加特定类型的元素,但在读取元素时的类型信息会模糊。由于我们不知道具体的类型是什么,所以只能以 Object 类型进行处理。

// 有界通配符的demo
// demo1(正面)
List<? super Integer> list = new ArrayList<>();
list.add(1); // 可以添加具体类型 Integer 的元素
list.add(2); // 可以添加具体类型 Integer 的元素
Object item = list.get(0); // 读取元素时的类型为 Object
System.out.println(item); // 输出: 1

// demo2(反面)
List<? super Integer> list = new ArrayList<>();
list.add(1); // 可以添加具体类型 Integer 的元素
list.add("hello"); // 编译错误:无法添加不符合下界的类型

以上是全部内容,感谢阅读。