数说Java字符串类型转枚举

在Java中字符串转枚举大致有两类方法:

一是使用JDK为枚举提供的valueOf方法转换,一个也是JDK提供的通过获取所有枚举对象的数组values方法,再遍历筛选得到目标枚举对象

先简单写一个枚举说明这两种方法

/**
 * @author SeasonSoy
 * @since 2019/9/20 11:53
 */
public enum Operator {

    ADD,
    SUBTRACT,
    MULTIPLY,
    DIVIDE
}

valueOf

使用方式

valueOf方法的使用方式是一个静态方法获取,参数是String类型,返回值是对应的类型

Operator operator = Operator.valueOf("ADD");
特点分析

但是如果需要使用这个方法,需要注意的是,这个方法会抛出两种异常,一个是NullPointerExceptionIllegalArgumentException

因为枚举底层生成的字节码都是调用的Enum类的valueOf方法,其源码如下:

public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
    T result = enumType.enumConstantDirectory().get(name);
    if (result != null)
        return result;
    if (name == null)
        throw new NullPointerException("Name is null");
    throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
}

很明显的可以看到,实际获取的方法是通过对应枚举的字节码对象,调用enumConstantDirectory()方法,这个方法返回的是一个Map<String, T>枚举对象的Map,就通过get(name)获取对应的对象

这时的result返回值有可能为null,如果不为空则返回,如果为空则抛出异常

另外再仔细看一下enumConstantDirectory()这个方法

    Map<String, T> enumConstantDirectory() {
        if (enumConstantDirectory == null) {
            T[] universe = getEnumConstantsShared();
            if (universe == null)
                throw new IllegalArgumentException(
                    getName() + " is not an enum type");
            Map<String, T> m = new HashMap<>(2 * universe.length);
            for (T constant : universe)
                m.put(((Enum<?>)constant).name(), constant);
            enumConstantDirectory = m;
        }
        return enumConstantDirectory;
    }

这个方法在Class类中,可访问范围是default;其中有一个比较关键的enumConstantDirectory成员,用来存枚举对象

private volatile transient Map<String, T> enumConstantDirectory = null;

这个方法大致干的事情就是:如果一个类的字节码如果是一个枚举类,可以通过把常量数组在这里封装成一个Map并返回,如果不是第一次调用,就直接返回已经封装好的Map

在上一段源码中还有一个getEnumConstantsShared()方法获取T[]数组比较重要,等会儿会在第二种方式里再说明

使用要点
  1. 无需再添加多的取值方法,可直接使用
  2. 在上层代码中,必须要处理好可能会抛出的异常
  3. 看情况对字符串参数调用一次toUpperCase(这点非必须,也不会再提)

values

以values为标题名并不是说,一定需要使用values方法,而是一种遍历过滤取值的思想

使用方式

其实本质上没什么差别,就是写法习惯上可能会有区别,而产生以下几种方式

方式一

可以在枚举内部直接提供一个方法,或者写在其他地方也行

    public Operator fromText(String operator) {
        Operator[] operators = Operator.values();
        for (Operator member : operators) {
            if (member.toString().equalsIgnoreCase(operator)) {
                return member;
            }
        }
        return null;
    }

朴实无华的代码,没什么好说的

方式二

如果是JDK1.8以上版本,可以使用stream,而且可以加强这个获取过程

    public Operator fromText(String operator) {
        return Arrays.stream(Operator.values())
                .filter(member -> member.toString().equalsIgnoreCase(operator))
                .findFirst()
                .orElse(null);
    }

加强的时候,可以做更多的流操作,比如添加更多的filter等等

方式三

虽然标题说是方式三,其实再整得花里胡哨的,也跟上述方式没什么区别,只是用来学习

上文有提到过,可以通过字节码获取,方法如下

Operator[] enumConstants = Operator.class.getEnumConstants();

剩下的和上面处理一样

至于这个Class提供的getEnumConstants()方法,可以看一看

    public T[] getEnumConstants() {
        T[] values = getEnumConstantsShared();
        return (values != null) ? values.clone() : null;
    }

这个public方法,直接提供了调用getEnumConstantsShared()方法的方式;更值得学习的是返回的values数组,调用了clone方法,我猜作者的意图是为了保护values的引用,因为只是一个浅拷贝,并不会影响其中的枚举对象

getEnumConstantsShared()的源码如下

    T[] getEnumConstantsShared() {
        if (enumConstants == null) {
            if (!isEnum()) return null;
            try {
                final Method values = getMethod("values");
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedAction<Void>() {
                        public Void run() {
                                values.setAccessible(true);
                                return null;
                            }
                        });
                @SuppressWarnings("unchecked")
                T[] temporaryConstants = (T[])values.invoke(null);
                enumConstants = temporaryConstants;
            }
            // These can happen when users concoct enum-like classes
            // that don't comply with the enum spec.
            catch (InvocationTargetException | NoSuchMethodException |
                   IllegalAccessException ex) { return null; }
        }
        return enumConstants;
    }

其中enumConstants是一个数组成员,定义如下

private volatile transient T[] enumConstants = null;

可以看出底层代码通过反射其实最终还是执行的values方法,而且也是为了防止在外部修改enumConstants的指针,才使用的克隆方法

不过使用这种方式,可以写一个更泛化的方法,作为工具类使用。以下是示例

    public static <T extends Enum> T parseEnum(String text, Class<T> clazz) {

        T[] enumConstants = clazz.getEnumConstants();
        if(Objects.isNull(enumConstants) || enumConstants.length == 0)
            return null;
        return Arrays.stream(enumConstants)
                .filter(member -> member.toString().equalsIgnoreCase(text))
                .findFirst()
                .orElse(null);
    }
另外

至于values()具体是怎么工作的,可以看一看这一篇《java枚举类型的实现原理》,其中比较详细的说明了反编译以后的枚举字节码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值