优化 java 中枚举类使用

682 阅读5分钟

当前的项目在设计的时候,后台负责处理转化页面上的select值。比如,页面下拉拿到的值就是 在线/离线,后台存储为 0/1,这个映射关系,由后台维护

第一稿

项目赶进度,直接用 map 一把梭哈,就是用的时候,稍微有点蛋疼。。。

public static Map<String, Integer> robotState = new HashMap<String, Integer>() {
		{
			// 车辆状态:0:在线,1:离线,2:禁用,3:维护
			put("在线", 0);
			put("离线", 1);
			put("禁用", 2);
			put("维护", 3);
		}
	}, robotWork = new HashMap<String, Integer>() {
		{
			// 工作状态:1:空闲,0:运行,2:充电,4:报警
			put("运行", 0);
			put("空闲", 1);
			put("充电", 2);
			put("报警", 3);
		}
	}
    // 下边还有一大堆。。。
	.........

不是很优雅,对吧。而且,这些都是可以修改的,万一哪天有傻x给我偷偷使绊子改了我定义的值呢?

public static String get(Map<String, Integer> map, Integer val) {
	for (String key : map.keySet()) {
		if (map.get(key) == val) {
			return key;
		}
	}
	return map.keySet().iterator().next();
}
public static Integer get(Map<String, Integer> map, String key) {
	val = map.get(key);
	if (val != null) {
		return val;
	}
	return map.values().iterator().next();
}

提供了两个静态的 get(),需要指定使用的哪个map,然后拿 key(或value) 换 value(或key),程序调用的方式 SelectVal.get(robotState, 1);

第二稿

将 map 数据,转化为枚举类,有一个要求就是,要根据给定的 key或 value,去换取另一个值

因为所有的枚举类,都要有这两个功能,规范起见,定义一个接口,提供两个默认实现,简化枚举类的定义

public abstract class EnumVal {
	default Integer get(Enum<? extends EnumVal> e, String val) {
		for (EnumVal ev : e.getDeclaringClass().getEnumConstants()) {
			if (ev.getStr().equals(val))
				return ev.getNum();
		}
		return 0;
	}
	default String get(Enum<? extends EnumVal> e, int val) {
		for (EnumVal ev : e.getDeclaringClass().getEnumConstants()) {
			if (ev.getNum() == val)
				return ev.getStr();
		}
		return "";
	}
	String getStr();
	Integer getNum();
}

然后枚举类就成了这个样子:

@AllArgsConstructor
@Getter
public enum StationState implements EnumVal {
	ON(	"在线",0),
	OFF("离线",1),
	FORBID(	"禁用",2),
	FIX("维护",3);
	private String str;
	private Integer num;
	
	private static StationState instance() {return ON;	}
    public static String get(Integer val) {
		return instance().get(instance(), val);
	}
	public static Integer get(String val) {
		return instance().get(instance(), val);
	}
}

emmm,最后的两个静态方法定义的是不是蠢了一点,需要手动给一个实例,然后通过这个实例去调接口上的 default 方法,可是,没有实例的话,那 default 方法就没法调用了啊,太难了

第三版

最近在看 java的泛型,突然一个抖机灵,发现,我可以吧枚举类上的方法调用,改为委托调用???就是我吧这操作的逻辑,扔到另一个公共类上,然后只需要在枚举类中提供两个静态方法,然后只需要在静态方法里访问公共类里的具体操作,这个不就是代理的感觉嘛

开整,在那个接口类中,定义一个包访问权限的公共类

public interface EnumVal {
	String getStr();
	Integer getNum();
}

class EnumValFunc {
	public static String getVal(Class<? extends EnumVal> clz, int val) {
		for (EnumVal ev : clz.getEnumConstants()) {
			if (ev.getNum() == val)
				return ev.getStr();
		}
		return "";
	}
	
	public static int getVal(Class<? extends EnumVal> clz, String val) {
		for (EnumVal ev : clz.getEnumConstants()) {
			if (ev.getStr().equals(val))
				return ev.getNum();
		}
		return 0;
	}
}

然后,枚举类中的静态方法调用,就变成了这:

public static String getVal(int val) {
	return EnumValFunc.getVal(CURR_CLZ_NAME, val);
}

public static int getVal(String val) {
	return EnumValFunc.getVal(CURR_CLZ_NAME, val);
}

// 通过创建匿名类的方式,获取当前的className
@SuppressWarnings("unchecked")
private static final Class<? extends EnumVal> CURR_CLZ_NAME = (Class<? extends EnumVal>) new Object() {}.getClass().getEnclosingClass();

怎么样,是不是看着特别舒服了,复制粘贴的时候,只需要修改下具体的枚举值就行

改到这里,发现那2个静态的方法,每次定义一个新的枚举类,都要抄一遍,略微有些嫌弃吧,觉得代码重复,但这个静态调用,确实对代码编写的体验还是很不错的,然后,折腾着,小改一版。。。

第三版 ’2

大致思路就是,去掉那个枚举操作的公共类,吧方法改回为接口的default方法,然后在枚举类中定义一个静态的 getInstance(),需要在接口中补充一个一个方法 getClz()

package com.rlzz.r9.po.visitor.enu;

public interface EnumVal {
	public default String getVal( int val) {
		for (EnumVal ev : getClz().getEnumConstants()) {
			if (ev.getNum() == val)
				return ev.getStr();
		}
		return "";
	}
	
	default int getVal( String val) {
		for (EnumVal ev : getClz().getEnumConstants()) {
			if (ev.getStr().equals(val))
				return ev.getNum();
		}
		return 0;
	}
	Class<? extends EnumVal> getClz();
	String getStr();
	Integer getNum();
}

枚举类中的方法就变成了这个样子:

	public static EnumVal getInstance() {
		return  CURR_CLZ_NAME.getEnumConstants()[0];
	}
	
	@Override
	public Class<? extends EnumVal> getClz() {
		return CURR_CLZ_NAME;
	}
	
	// 通过创建匿名类的方式,获取当前的className
	@SuppressWarnings("unchecked")
	private static final Class<? extends EnumVal> CURR_CLZ_NAME = (Class<? extends EnumVal>) new Object() {}.getClass().getEnclosingClass();
	

emmmm,这改完后,代码量没啥变化,反而调用的时候,要先通过 getInstance()获取实例再操作,多了一步,似乎更麻烦了些。。。

先不管了吧,两种方案看着都差不多,过两天说不定能想明白点


其实,我突然想到了通过类实例调用的优势,那就是,如果我的通用方法要是有所变化,比如,要扩展的话,只需要在接口里加个 default 的方法就完事;前一种的话,就需要回过头去每个枚举类中补充新的静态方法

就先写到这里吧

--------------------------------------- 2020/10/19 ---------------------------------------

后续

吧全部的 map数据翻译成枚举类,代码里其他的地方都还没跟着变,复制粘贴再改改,枚举实例命全靠有道翻译。看着多出来的几十个枚举类,陷入沉思,我是个憨批吗???这工作量明明大了好多嘛,淦。整完发现还是 map 的方式香。。。

刚好看到 java11的新特性,不可变集合类??!刚好解决map数据可能被修改的情况

尝试在 java8 里嗦一哈 immutablemap

不错,当前项目刚好引用了谷歌提供的集合工具,不可变map中有一个方法刚好合适:

ImmutableMap.copyOf(Map<? extends K, ? extends V>)

现在的map声明就变成了:

	public static Map<String, Integer> robotState = ImmutableMap.copyOf(new HashMap<String, Integer>() {
		{
			put("在线", 0);
			put("离线", 1);
			put("禁用", 2);
			put("维护", 3);
		}
	})

ctrl + f 批量替换补个头和尾,完事