java中枚举表示数据状态

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());
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342