1 场景
本文主要讲下java程序中对于状态数据
的状态代码
和状态名称
如何管理的问题。
对于数据的状态,程序中,经常不会存储状态的名称
,而会存储状态对应的代码
。
每个java后端开发,面对数据库中的一个字段注释:
del_flag 删除状态(0:正常;1:删除;)
都会纠结一个问题,在java代码中,这个字段delFlag值
对应的0
和1
和分别对应的含义正常
、删除
,写在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());
}