美文网首页Java高级架构师之路
java中枚举表示数据状态

java中枚举表示数据状态

作者: 水煮鱼又失败了 | 来源:发表于2020-10-04 10:59 被阅读0次

    1 场景

    本文主要讲下java程序中对于状态数据状态代码状态名称如何管理的问题。

    对于数据的状态,程序中,经常不会存储状态的名称,而会存储状态对应的代码

    每个java后端开发,面对数据库中的一个字段注释:

    del_flag  删除状态(0:正常;1:删除;)
    

    都会纠结一个问题,在java代码中,这个字段delFlag对应的01和分别对应的含义正常删除,写在java代码的哪个地方比较合适?

    很多人认为这个对于很大的项目来说无非是个很小的事,写在哪里,无所谓,都不会影响我们的项目。讲道理,这个无法反驳。

    这篇文章,让我们一起剖析下这件微不足道的小事。

    在这里,我们为了方便,定义状态代码状态名称的类型均为字符串

    2 步骤

    2.1 直接使用

    最简单的用法,就是直接在程序中使用状态的代码,如下。

    //(1)设置删除标记为正常
    record.setDelFlag("0");
    ......
    
    //(2)判断删除标志
    if("0".equals(record.getDelFlag())){
        return "正常";
    }
    
    2.1.1 优点

    项目初期管理简单,不用花费时间进行代码封装。

    2.1.2 缺点

    (1)项目中出现大量手写状态代码状态名称。代码检查工具会提示魔法值,如项目中对代码检查要求高,此部分代码无法合并主干代码。

    (2)如果状态代码对应的状态名称发生了变动,全局更改起来,将是十分致命的。

    (3)研发人员水平不一致,手动在代码里写状态代码和状态名称,十分容易写错

    2.2 静态常量

    部分项目,会定义一个java类,将状态码定义为其中的静态常量。其中定义了项目中用到的所有状态代码

    public class StateContext{
        /**
         * 正常
         */
        public static final String NORMAL="0";
        
        /**
         * 删除
         */
        public static final String DEL="1";
        //其他状态代码
        ......
    }
    

    静态常亮的使用方式如下:

    //(1)设置删除标记为正常
    record.setDelFlag(StateContext.NORMAL);
    ......
    
    //(2)判断删除标志
    if(StateContext.NORMAL.equals(record.getDelFlag())){
        return "正常";
    }
    
    2.2.1 优点

    状态代码,可以直接通过类的静态变量使用(状态名称,也可以通过类的静态变量定义)。

    2.2.2 缺点

    (1)需要在类中大量定义静态变量,使用时,需十分明确使用的变量的名字,勿使用成别的变量。

    (2)一般只通过静态变量定义状态代码状态名称也可这么定义。使用时,对应关系容易对应不上。

    2.3 枚举

    每种状态信息,定义专门的枚举类。

    /**
     * 删除标志枚举
     */
    public enum DelFlagEnum{
        
        NORMAL("0","正常"),
        DEL("1","删除");
        
        private DelFlagEnum(String code,String name){
            this.code=code;
            this.name=name;
        }
        
        /**
         * 代码
         */
        private String code;
        
        /**
         * 名称
         */
        private String name;
        
        public String getCode() {
            return code;
        }
        
        public String getName() {
            return name;
        }
        
    }
    
    2.3.1 优点

    (1)每种状态定义一个专门的枚举类,枚举类中的定义十分明确

    (2)可通过枚举属性准确对应的状态代码和状态名称。

    (3)定义的枚举是有属性的,可以在枚举中定义专门的特殊方法,应对特殊的业务。

    2.3.2 缺点

    枚举主动在代码中使用,是没有问题的。如下:

    DelFlagEnum.NORMAL.getCode();
    

    但是状态代码一般都会存储在数据库中,如果通过数据库中获取的状态代码获取枚举对象,基础的枚举信息,无法实现此功能。

    故可以在枚举中,定义专门的方法,通过状态代码获取枚举或通过状态名称获取枚举。如下

    import org.apache.commons.collections4.CollectionUtils;
    import org.apache.commons.lang3.EnumUtils;
    import org.apache.commons.lang3.StringUtils;
    import java.util.List;
    
    /**
     * 删除标志枚举
     */
    public enum DelFlagEnum{
        
        NORMAL("0","正常"),
        DEL("1","删除");
        
        private DelFlagEnum(String code,String name){
            this.code=code;
            this.name=name;
        }
        
        /**
         * 枚举集合
         */
        private static final List<DelFlagEnum> ENUM_LIST= EnumUtils.getEnumList(DelFlagEnum.class);
        
        /**
         * 通过代码获取枚举
         * @param code 枚举代码
         * @return 枚举
         */
        public static DelFlagEnum getEnumByCode(String code){
            if(StringUtils.isNotEmpty(code) && CollectionUtils.isNotEmpty(ENUM_LIST)){
                for(DelFlagEnum delFlagEnum:ENUM_LIST){
                    if(code.equals(delFlagEnum.getCode())){
                        return delFlagEnum;
                    }
                }
            }
            return null;
        }
        
        /**
         * 通过名称获取枚举
         * @param name 枚举名称
         * @return 枚举
         */
        public static DelFlagEnum getEnumByName(String name){
            if(StringUtils.isNotEmpty(name) && CollectionUtils.isNotEmpty(ENUM_LIST)){
                for(DelFlagEnum delFlagEnum:ENUM_LIST){
                    if(name.equals(delFlagEnum.getName())){
                        return delFlagEnum;
                    }
                }
            }
            return null;
        }
        
        /**
         * 代码
         */
        private String code;
        
        /**
         * 名称
         */
        private String name;
        
        public String getCode() {
            return code;
        }
        
        public String getName() {
            return name;
        }
    }
    
    

    但是,如果每个枚举都是这样写。代码量大大增加,极易出现拷贝导致的低级错误。因此需对此代码进行封装,减少开发人员的工作量。

    2.4 枚举+命名空间

    此种方式,是对2.3中枚举定义状态值进行了代码封装

    这是作者当前找到的最优的方式,建议使用。

    2.4.1 枚举基础接口

    定义基础枚举接口。接口中定义枚举中必有的两个方法(获取代码、获取名称)

    public interface BaseEnum {
        /**
         * 获取代码
         * @return
         */
        String getCode();
        
        /**
         * 获取名称
         * @return
         */
        String getName();
    }
    
    2.4.2 枚举实现类

    每种状态对应专门的枚举类,每个枚举类均需实现接口BaseEnum(javac编译器,枚举无法继承,所以成员变量、接口方法,还需要手动写)。

    枚举类中仍然可以定义专门的方法,来执行特殊业务。

    public enum DelFlagEnum implements BaseEnum{
        
        NORMAL("0","正常"),
        DEL("1","删除");
        
        /**
         * 代码
         */
        private String code;
        
        /**
         * 名称
         */
        private String name;
        
        private DelFlagEnum(String code, String name){
            this.code=code;
            this.name=name;
        }
        
        @Override
        public String getCode() {
            return this.code;
        }
        
        @Override
        public String getName() {
            return this.name;
        }
    }
    
    2.4.3 枚举命名空间

    可以通过此命名空间实现如下功能:

    (1)通过代码获取枚举(时间复杂度O(1))

    (2)通过名称获取枚举(时间复杂度O(n))

    上述两个方法均线程安全,且均为懒加载,即用到方法时才去初始化对应的枚举类缓存。

    import org.apache.commons.collections4.MapUtils;
    import org.apache.commons.lang3.StringUtils;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * 枚举命名空间
     */
    public class EnumContext {
        
        /**
         * 枚举集合映射
         */
        private static final Map<String, Map<String, BaseEnum>> ENUM_MAP = new ConcurrentHashMap<>();
        
        /**
         * 初始化枚举map
         */
        private synchronized static <T extends BaseEnum> void initEnumMap(Class<T> enumClass) {
            if (enumClass != null && enumClass.isEnum()) {
                String key = enumClass.getName();
                if (!ENUM_MAP.containsKey(key)) {
                    Map<String, BaseEnum> map = new ConcurrentHashMap<>();
                    BaseEnum[] baseEnums = enumClass.getEnumConstants();
                    if (baseEnums != null && baseEnums.length > 0) {
                        for (final BaseEnum e : baseEnums) {
                            map.put(e.getCode(), e);
                        }
                    }
                    ENUM_MAP.put(key, map);
                }
            }
        }
        
        /**
         * 通过代码获取枚举(时间复杂度O(1))
         *
         * @param code      代码
         * @param enumClass 枚举类
         * @return 枚举
         */
        public static <T extends BaseEnum> T getEnumByCode(String code, Class<T> enumClass) {
            if (StringUtils.isNotEmpty(code) && enumClass != null) {
                String key = enumClass.getName();
                if (!ENUM_MAP.containsKey(key)) {
                    //如果不存在=>初始化
                    initEnumMap(enumClass);
                }
                //如果已存在=>判断
                Map<String, BaseEnum> map = ENUM_MAP.get(key);
                if (MapUtils.isNotEmpty(map)) {
                    return (T) map.get(code);
                }
            }
            return null;
        }
        
        /**
         * 通过名称获取枚举(时间复杂度O(n))
         *
         * @param name      代码
         * @param enumClass 枚举类
         * @return 代码
         */
        public static <T extends BaseEnum> T getEnumCodeByName(String name, Class<T> enumClass) {
            if (StringUtils.isNotEmpty(name) && enumClass != null) {
                String key = enumClass.getName();
                if (!ENUM_MAP.containsKey(key)) {
                    //如果不存在=>初始化
                    initEnumMap(enumClass);
                }
                //如果已存在=>判断
                Map<String, BaseEnum> map = ENUM_MAP.get(key);
                if (MapUtils.isNotEmpty(map)) {
                    for (Map.Entry<String, BaseEnum> entry : map.entrySet()) {
                        if (entry.getValue() != null && name.equals(entry.getValue().getName())) {
                            return (T) entry.getValue();
                        }
                    }
                }
            }
            return null;
        }
    }
    
    2.4.3 使用
    //直接使用枚举
    System.out.println(DelFlagEnum.NORMAL.getCode() + "  " + DelFlagEnum.NORMAL.getCode());
    
    //通过代码获取枚举
    DelFlagEnum normalEnum = EnumContext.getEnumByCode("0", DelFlagEnum.class);
    if (normalEnum != null) {
        System.out.println(normalEnum.getCode() + "  " + normalEnum.getName());
    }
    
    //通过名称获取枚举
    DelFlagEnum delEnum = EnumContext.getEnumCodeByName("删除", DelFlagEnum.class);
    if (delEnum != null) {
        System.out.println(delEnum.getCode() + "  " + delEnum.getName());
    }
    

    相关文章

      网友评论

        本文标题:java中枚举表示数据状态

        本文链接:https://www.haomeiwen.com/subject/qqcnuktx.html