枚举最佳实践

概述

java世界数据分两种类型,基本数据类型和引用数据类型。引用数据类型分为类和接口。枚举是一种特殊的类,注解是一种特殊的接口。

基于以上可知,枚举是一种特殊的类,所谓的enum关键字其实是编译器语法糖。每一个枚举类编译之后反编译得到的依然是class。这个class继承了java.lang.Enum类,顶层父类依然是Object 。只不过这个类的构造方法是私有的,只是通过类里面的静态域持有了有限个本枚举类的实例。类的具体结构入标题图所示。

综上可知,枚举是一种特殊的类。这个类的结构和这个类的有限多个实例对象放在一起,形成了枚举类。有了基本的概念之后,分享如下几点。

FastJson中的序列化特性枚举

com.alibaba.fastjson.parser.Feature

package com.alibaba.fastjson.parser;

/**
 * @author wenshao[szujobs@hotmail.com]
 */
/**
 * 特性枚举类,
 * 每个枚举的mask占用int的32个bit位的不同位域
 */
public enum Feature {
    /**
     * 枚举实例1
     * ordinal 默认值:0
     * <p>
     * mask:0b0000_0001
     */
    AutoCloseSource,
    /**
     * 枚举实例2
     * ordinal 默认值:1
     * mask:0b0000_0010
     */
    AllowComment,
    /**
     * 枚举实例3
     * ordinal 默认值:2
     * mask:0b0000_0100
     */
    AllowUnQuotedFieldNames,
    /**
     * 枚举实例4
     * ordinal 默认值:3
     * mask:0b0000_1000
     */
    AllowSingleQuotes,
    /**
     * 枚举实例5
     * ordinal 默认值:4
     * mask:0b0001_0000
     */
    InternFieldNames,
    /**
     * 枚举实例6
     * ordinal 默认值:5
     * mask:0b0010_0000
     */
    AllowISO8601DateFormat;

    Feature() {
        //在构造方法中将1左移每个枚举实例的ordinal位,得到实例字段 掩码mask值
        mask = (1 << ordinal());
    }

    public final int mask;

    public final int getMask() {

        return mask;
    }

    //枚举类的静态方法,检查一个int值是否包含某个制定特性,通过位操作,得到的结果大于0则说明int值在指定枚举的位域上有值
    public static boolean isEnabled(int features, Feature feature) {

        return (features & feature.mask) != 0;
    }

    //叠加int值,将需要包含的枚举值得mask通过位操作叠加到int值上,包含特性用|,不包含用& ~
    public static int config(int features, Feature feature, boolean state) {

        if (state) {
            features |= feature.mask;
        } else {
            features &= ~feature.mask;
        }

        return features;
    }
    //将一个特性数据转换成特性int
    public static int of(Feature[] features) {

        if (features == null) {
            return 0;
        }

        int value = 0;

        for (Feature feature : features) {
            value |= feature.mask;
        }

        return value;
    }
}```

如上所示,FastJSON中,有一个枚举用来定义序列化过程的特性。利用枚举类中每个实例都默认生成的ordinal值,让int 类型的1左移。利用位域的互斥,得到每个枚举实例的掩码 mask。这样的好处是可以通过一个int值包含的1的位置来得到包含的枚举值。在传递参数时,我们只需要传递一个int值,就可以利用静态方法isEnable来检查是否包含此特性。一个int值可以有32个位域,也就是可以定义32个枚举实例。如果还不够,用long类型,包含64个位域。一般足够啦。

JAVA并发工具包中的时间单位枚举

java.util.concurrent.TimeUnit

/** * 时间单位枚举
/**
 * 时间单位枚举
 * 枚举类中不丰富部分方法实现了,用作各个枚举实例的公用方法。
 * 还有部分方法空实现,并抛了异常,用于被枚举实例覆盖重写。
 * 实现了某种层次上的策略模式。
 * 枚举其实还可以实现接口,同时也可以定义抽象方法。
 * 抽闲方法被枚举实例通过匿名内部类的方式实现。
 */
public enum TimeUnit {
    /**
     * Time unit representing one thousandth of a microsecond
     */
    //枚举实例
    NANOSECONDS {
        //重写覆盖枚举类中定义的空实现的方法。
        public long toNanos(long d) {

            return d;
        }

        public long toMicros(long d) {

            return d / (C1 / C0);
        }

        public long toMillis(long d) {

            return d / (C2 / C0);
        }

        public long toSeconds(long d) {

            return d / (C3 / C0);
        }

        public long toMinutes(long d) {

            return d / (C4 / C0);
        }

        public long toHours(long d) {

            return d / (C5 / C0);
        }

        public long toDays(long d) {

            return d / (C6 / C0);
        }

        public long convert(long d, TimeUnit u) {

            return u.toNanos(d);
        }

        int excessNanos(long d, long m) {

            return (int) (d - (m * C2));
        }
    };

    // Handy constants for conversion methods
    static final long C0 = 1L;

    static final long C1 = C0 * 1000L;

    static final long C2 = C1 * 1000L;

    static final long C3 = C2 * 1000L;

    static final long C4 = C3 * 60L;

    static final long C5 = C4 * 60L;

    static final long C6 = C5 * 24L;

    static final long MAX = Long.MAX_VALUE;

    static long x(long d, long m, long over) {

        if (d > over) return Long.MAX_VALUE;
        if (d < -over) return Long.MIN_VALUE;
        return d * m;
    }

    public long convert(long sourceDuration, TimeUnit sourceUnit) {

        throw new AbstractMethodError();
    }


    public long toNanos(long duration) {

        throw new AbstractMethodError();
    }

    public long toMicros(long duration) {

        throw new AbstractMethodError();
    }


    public long toMillis(long duration) {

        throw new AbstractMethodError();
    }


    public long toSeconds(long duration) {

        throw new AbstractMethodError();
    }


    public long toMinutes(long duration) {

        throw new AbstractMethodError();
    }


    public long toHours(long duration) {

        throw new AbstractMethodError();
    }

    public long toDays(long duration) {

        throw new AbstractMethodError();
    }


    abstract int excessNanos(long d, long m);


    public void timedWait(Object obj, long timeout)
            throws InterruptedException {

        if (timeout > 0) {
            long ms = toMillis(timeout);
            int ns = excessNanos(timeout, ms);
            obj.wait(ms, ns);
        }
    }


    public void timedJoin(Thread thread, long timeout)
            throws InterruptedException {

        if (timeout > 0) {
            long ms = toMillis(timeout);
            int ns = excessNanos(timeout, ms);
            thread.join(ms, ns);
        }
    }


    public void sleep(long timeout) throws InterruptedException {

        if (timeout > 0) {
            long ms = toMillis(timeout);
            int ns = excessNanos(timeout, ms);
            Thread.sleep(ms, ns);
        }
    }

}

该枚举中,展示了枚举实例和枚举类的关系。枚举类其实就是一个普通的类,也就是说可以被匿名实例化。通过匿名实例化,可以重写覆盖枚举的方法。实现某种程度上的策略模式。

枚举可以实现接口

public enum ConfigurationPlatform implements keyValueLoaderProvider {
    DATABASE() {
        @Override
        public DynamicValueLoader keyValueLoader() {

            return null;
        }
    },
    ZOOKEEPER() {
        @Override
        public DynamicValueLoader keyValueLoader() {

            return null;
        }
    };
}

枚举实现接口后,实现了枚举的拓展性,通过多态,同一类型的枚举不一定需要放在统一个枚举类里,只要实现了同一个接口,他们就具有同样的功能。参数化类型为<? extends 接口 & Enum>。即如下:

该枚举实现了Function接口

该枚举实现了Function接口

通过向上转型得到集合

通过向上转型得到集合

枚举和反射

枚举和反射

枚举和反射

java中所有的类都被抽象成了Class对象,因此Class类对象囊括了枚举类这个特性,也提供了枚举类相关的方法。

 	//判断一个Class对象是否是枚举类	
	public boolean isEnum() {
        // An enum must both directly extend java.lang.Enum and have
        // the ENUM bit set; classes for specialized enum constants
        // don't do the former.
        return (this.getModifiers() & ENUM) != 0 &&
        this.getSuperclass() == java.lang.Enum.class;
    }
  	//得到一个枚举类的所有枚举实例  
    public T[] getEnumConstants() {
        T[] values = getEnumConstantsShared();
        return (values != null) ? values.clone() : null;
    }

如果配合接口,就可以实现这样的业务场景:
我们总是在页面上会有下拉选择框,下拉选择框是的值是离散的。这种场景适合用枚举。我们为每个下拉选择框定义一个枚举,枚举实现一个接口,定义code()和name()方法。然后通过反射得到这个枚举的所有实例,转型调用name和code方法返回给前端动态生成下拉列表。这种实现的好处在于,前端传递的每一个值,在后端都能匹配一个枚举,同时如果枚举实例增加了,前端生成的下拉列表选项也能动态增加。代码如下:

用于标记select枚举的注解

用于标记select枚举的注解

被标记注解标记的枚举。定义了name和code字段

被标记注解标记的枚举。定义了name和code字段

扫描的到字节码对象,反射调用方法,生成SELECT_ITEMS。用于返回给前端构造动态的select下拉列表

扫描的到字节码对象,反射调用方法,生成SELECT_ITEMS。用于返回给前端构造动态的select下拉列表

总结

以上总结了我对于java枚举的主要认识。欢迎补充。我个人也会随时补充哒~

原文地址:https://www.cnblogs.com/yungyu16/p/8662105.html