Java中如何根据值获取泛型枚举对象及EnumUtil的必要性

一、抽取EnumUtil的必要性

比如说,我在业务中定义了一个表示“加密类型”的枚举类 EncryptType

import cn.hutool.core.util.StrUtil;
import com.suning.tech.exception.GatewayRuntimeException;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Arrays;
import java.util.Optional;

/**
 * 功能描述:加密类型
 */
@AllArgsConstructor
@Getter
public enum EncryptType {

    NONE (0), // 不加密
    AES(1), // 1:aes
    RSA(2), // 2:rsa
    XXTEA(3); // 3:xxtea

    int type;

    public static EncryptType parseInt(int typeVal) {
        Optional<EncryptType> result = Arrays.stream(values()) // values() 可以获取当前枚举类所有枚举常量
            .filter(t -> t.getType() == typeVal) // 判断相等的条件
            .findFirst();
        if (result.isPresent()) {
            return result.get();
        } else {
            throw new GatewayRuntimeException(StrUtil.format("No EncryptType matches type value {}", typeVal)); // NOSONAR
        }
    }
}

这样做的好处是

  1. 避免客户端代码中的魔法值;
  2. 客户端代码更加清晰明了;
EncryptType encryptType = EncryptType.parseInt(type);
if (EncryptType.XXTEA.equals(encryptType)) {
  // XXTEA 加密
} else if (EncryptType.AES.equals(encryptType)) {
  // AES 加密
} else if (EncryptType.RSA.equals(encryptType)) {
  // RSA 加密
}

你可以想象一下,如果直接拿 int 类型的type 和 1,2,3 做比较,代码看起来会有多糟糕。

但是,枚举类型使用得多了以后,需要在每一个枚举类中,都写一段 parseInt 类型的代码,那得多糟心!

比如,我的项目中,就有这么多枚举类型,都需要增加解析方法!

这不够简洁啊!!! 于是,我尝试用泛型来抽取这段方法。

二、我的方案

import cn.hutool.core.util.StrUtil;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Optional;

/**
 * 功能描述: 枚举工具类
 *
 * @author geekziyu
 * @version 1.0.0
 */
public class EnumUtil {

    public static <E extends Enum<E>> E valueOf(Class<E> e, String typeValue) {
        E[] values = e.getEnumConstants();
        Optional<E> result = Arrays.stream(values)
                .filter(t -> String.valueOf(getTypeValue(t)).equals(typeValue)) // 用字符串形式比较
                .findFirst();
        if (result.isPresent()) {
            return result.get();
        } else {
            throw new RuntimeException(StrUtil.format("No {} matches type value {}", e.getTypeName(), typeValue)); // NOSONAR
        }
    }

    public static <E extends Enum<E>> E valueOf(Class<E> e, int typeValue) {
        return valueOf(e, String.valueOf(typeValue));
    }


    private static <E extends Enum<E>> Object getTypeValue(E e) {
        Class<? extends Enum> typeClass = e.getClass();
        try {
            Method getter = typeClass.getMethod("getTypeValue");
            ReflectionUtils.makeAccessible(getter);
            return getter.invoke(e);
        } catch (NoSuchMethodException ex) {
            throw new RuntimeException(StrUtil.format("No such method named 'getTypeValue' in Enum {}!", typeClass.getTypeName())); // NOSONAR
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(StrUtil.format("Inaccessible is the method 'getTypeValue' in Enum {} !", typeClass.getTypeName()), ex.getCause()); // NOSONAR
        } catch (InvocationTargetException ex) {
            throw new RuntimeException(StrUtil.format("Exception occurred to method 'getTypeValue' in  Enum {}!", typeClass.getTypeName()), ex.getCause()); // NOSONAR
        }
    }
}

当然,我这个工具类也有局限性,那就是我限定了枚举类中,必须要有一个名为 typeValue 的字段且必须要有 getTypeValue 方法!

三、hutool的解决方案

public static <E extends Enum<E>> E likeValueOf(Class<E> enumClass, Object value) {
  if (value instanceof CharSequence) {
    value = value.toString().trim();
  }

  final Field[] fields = ReflectUtil.getFields(enumClass);
  final Enum<?>[] enums = enumClass.getEnumConstants();
  String fieldName;
  for (Field field : fields) {
    fieldName = field.getName();
    if (field.getType().isEnum() || "ENUM$VALUES".equals(fieldName) || "ordinal".equals(fieldName)) {
      // 跳过一些特殊字段
      continue;
    }
    for (Enum<?> enumObj : enums) {
      if (ObjectUtil.equal(value, ReflectUtil.getFieldValue(enumObj, field))) {
        return (E) enumObj;
      }
    }
  }
  return null;
}

EncryptType 为例,有以下字段:

字段 field.getType().isEnum() 类型 fieldName
EncryptType.NONE true EncryptType "NONE"
EncryptType.AES true EncryptType "AES"
EncryptType.RSA true EncryptType "RSA"
EncryptType.XXTEA true EncryptType "XXTEA"
EncryptType.type false int "type"
EncryptType.$VALUES false EncryptType[] "$VALUES"
EncryptType.name false String "name"
EncryptType.ordinal false int "ordinal"

ObjectUtil.equal 的最普遍的“相等”判断依据是 (a == b) || (a != null && a.equals(b))

posted @ 2022-03-11 13:39  极客子羽  阅读(2076)  评论(0编辑  收藏  举报