• 枚举最佳实践


    概述

    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枚举的主要认识。欢迎补充。我个人也会随时补充哒~

  • 相关阅读:
    html常用标签及示例
    判断一个数是否是素数的讨论
    图像的空间域变化
    图像增强的点运算(一)
    字符串匹配——KMP
    AcWing1134最短路计数(spfa)
    AcWing1137拯救大兵瑞恩(双端队列搜索,状态压缩,分层图最短路)
    AcWing1175电路维修(双端队列+搜索)
    AcWing1137选择最佳线路(最短路)
    AcWing342道路与航线(dijkstra+拓扑排序)
  • 原文地址:https://www.cnblogs.com/yungyu16/p/8662105.html
Copyright © 2020-2023  润新知