一、引言
枚举是我们在编码过程中是非常常用的一种类,特别是在一些类型,状态定义的时候,我们都建议使用枚举来定义和校验。
前面写过一篇文章介绍怎么实现枚举的校验,《Hibernate Validator:自定义注解与校验器,为枚举数据校验赋能》
这一篇文章是说另外一种场景,一些复杂的枚举定义由接口定义,方便后续扩展,这个时候我们怎么快速将枚举类型转换为LabelValue形式。
二、枚举与LabelValue形式的定义
2.1 枚举的定义与特点
枚举的基本语法和结构、以及什么是枚举,和Java普通类的区别这里不多做赘述,我们直接说说一般要用来做枚举数据类型都是怎么定义的。
- 定义一个接口约束get方法
/**
* 定义一个接口约束get方法
*
* @author xxx
* @version 1.0
* @date 2025/6/24 20:54
*/
public interface EnumInterface {
Integer getValue();
String getLabel();
}
- 枚举定义案例
/**
* 任务执行状态枚举
*
* @author xxx
* @version 1.0
* @date 2025/6/24 20:53
*/
public enum TaskExecuteStatus implements EnumInterface {
TO_BE_STARTED(0, "待启动"),
TO_BE_EXECUTED(1, "待执行"),
UNDER_EXECUTION(2, "执行中"),
EXECUTION_COMPLETED(3, "执行完毕"),
EXECUTION_FAILED(4, "执行失败");
private final Integer value;
private final String label;
TaskExecuteStatus(Integer value, String label) {
this.value = value;
this.label = label;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String getLabel() {
return label;
}
}
2.2 LabelValue形式的介绍
LabelValue形式(键值对形式)是一种常见的数据结构,通常用于表示具有唯一标识符(键)和对应描述(值)的数据对。它在编程、数据存储、前端展示以及各种数据交互场景中被广泛使用。
2.2.1 基本概念
- Label(标签/键) :用于唯一标识一个数据项,通常是字符串类型,但在某些情况下也可以是数字或其他类型。
- Value(值) :与Label对应的描述或实际数据内容,可以是字符串、数字、对象等,具体类型取决于应用场景。
2.2.2 常见应用场景
应用场景别的有没有咱不讨论,这里就说说前端下拉框使用 在Web开发中,下拉框通常需要一个选项列表,每个选项包含一个显示给用户的文本(Label)和一个实际提交给后端的值(Value)。例如:
<select>
<option value="1">选项1</option>
<option value="2">选项2</option>
<option value="3">选项3</option>
</select>
三、转换的必要性
3.1 复杂枚举接口定义的必要性
在编程中,通过接口定义枚举具有诸多优势,特别是在便于扩展和维护方面,
从上图可以看出,使用接口定义枚举数据类型,我们可以非常灵活的进行扩展,同样也可以一键进行枚举数据类型校验,而不用担心客户端版本不一致导致枚举数据类型的差异问题。
3.2 手动转换的痛点
一般我们将枚举转换为LabelValue这种形式,都要进行手动转换,手动转换有下面这些痛点,导致我们开发起来相当难受。
-
手动编写转换逻辑的繁琐性。
-
手动转换容易出错,且难以维护。
下面是一种手动转换的方式:
import com.google.common.collect.Lists;
import java.io.Serializable;
import java.util.List;
/**
* 任务执行状态枚举
*
* @author xxx
* @version 1.0
* @date 2025/6/24 20:53
*/
public enum TaskExecuteStatus implements EnumInterface {
TO_BE_STARTED(0, "待启动"),
TO_BE_EXECUTED(1, "待执行"),
UNDER_EXECUTION(2, "执行中"),
EXECUTION_COMPLETED(3, "执行完毕"),
EXECUTION_FAILED(4, "执行失败");
private final Integer value;
private final String label;
TaskExecuteStatus(Integer value, String label) {
this.value = value;
this.label = label;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String getLabel() {
return label;
}
public static List<LabelVo> toList() {
List<LabelVo> list = Lists.newArrayList();
Lcc.TaskExecuteStatusEnum[] enums = Lcc.TaskExecuteStatusEnum.values();
for (Lcc.TaskExecuteStatusEnum e : enums) {
LabelVo vo = new LabelVo();
vo.setLabel(e.getLabel());
vo.setValue(e.getValue());
list.add(vo);
}
return list;
}
}
class LabelVo implements Serializable {
private Object value;
private String label;
public LabelVo(String value, String label) {
this.value = value;
this.label = label;
}
public LabelVo() {
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}
这里面的toList()方法,就是一种转换的手动写法,如果像这样子写,我们需要再每一个枚举类里面写这么一个方法,可谓是相当的繁琐。
四、干货:优雅的转换工具类
- 话不多说,咱先上工具类;这个工具类使用了反射,至于反射是什么,这就不是这篇文章要说明的了。
import com.google.common.collect.ImmutableMap;
import com.hytto.common.util.XKUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <b>抽取枚举的一些公共方法 </b><br><br>
* toSelections 枚举生成List <br>
* toMap 枚举生成Map <br>
* getEnum 根据某个方法值获取 <br>
* contains 判断某个值在这个枚举是否存在 <br>
*
* @author xxx
* @version 1.0
* @date 2025/6/25 18:03
*/
public class EnumMethodUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(EnumMethodUtil.class);
private static final String DEFAULT_METHOD_1 = "getLabel";
private static final String DEFAULT_METHOD_2 = "getValue";
private static final String NAME_METHOD_NAME = "name";
private static final String LABEL_NAME = "label";
private static final String VALUE_NAME = "value";
public static <T extends Enum<T>> List<Map<String, Object>> toSelections(Class<T> enumClass) {
return toSelections(enumClass, DEFAULT_METHOD_1, DEFAULT_METHOD_2, null);
}
public static <T extends Enum<T>> List<Map<String, Object>> toSelections(Class<T> enumClass, List<String> ignoredNames) {
return toSelections(enumClass, DEFAULT_METHOD_1, DEFAULT_METHOD_2, ignoredNames);
}
public static <T extends Enum<T>> List<Map<String, Object>> toSelections(Class<T> enumClass, String labelMethodName, String valueMethodName) {
return toSelections(enumClass, labelMethodName, valueMethodName, null);
}
/**
* 枚举返回下拉框选择器的数据集合
*
* @param enumClass 枚举类
* @param labelMethodName 作为 label 的方法名称
* @param valueMethodName 作为 value 的方法名称
* @param ignoredNames 要忽略的name值
* @param <T> 枚举类型
* @return List 集合,["label":"label","value":Object]
*/
public static <T extends Enum<T>> List<Map<String, Object>> toSelections(Class<T> enumClass, String labelMethodName, String valueMethodName, List<String> ignoredNames) {
T[] values = enumClass.getEnumConstants();
List<Map<String, Object>> selections = new ArrayList<>(values.length);
for (T value : values) {
try {
Class<?> tClass = value.getClass();
Method nameMethod = tClass.getMethod(NAME_METHOD_NAME);
String name = (String) nameMethod.invoke(value);
if (XKUtils.isEmpty(ignoredNames) || !ignoredNames.contains(name)) {
Method labelMethod = tClass.getMethod(labelMethodName);
Method valMethod = tClass.getMethod(valueMethodName);
String label = (String) labelMethod.invoke(value);
Object val = valMethod.invoke(value);
selections.add(ImmutableMap.of(LABEL_NAME, label, VALUE_NAME, val));
}
} catch (Exception exception) {
LOGGER.error(exception.getMessage());
}
}
return selections;
}
public static <T extends Enum<T>, K, V> Map<K, V> toMap(Class<T> enumClass) {
return toMap(enumClass, DEFAULT_METHOD_2, DEFAULT_METHOD_1, null);
}
public static <T extends Enum<T>, K, V> Map<K, V> toMap(Class<T> enumClass, String keyMethodName, String valueMethodName) {
return toMap(enumClass, keyMethodName, valueMethodName, null);
}
/**
* 枚举返回对应的Map数据类型
*
* @param enumClass 枚举类
* @param keyMethodName key值的对应的方法名
* @param valueMethodName value值的对应的方法名
* @param ignoredNames 要忽略的name值
* @param <T> 枚举类型
* @param <K> KEY值数据类型
* @param <V> Value值数据类型
* @return Map<Key, Value>
*/
public static <T extends Enum<T>, K, V> Map<K, V> toMap(Class<T> enumClass, String keyMethodName, String valueMethodName, List<String> ignoredNames) {
T[] values = enumClass.getEnumConstants();
Map<K, V> map = new HashMap<>();
for (T value : values) {
try {
Class<?> tClass = value.getClass();
Method nameMethod = tClass.getMethod(NAME_METHOD_NAME);
String name = (String) nameMethod.invoke(value);
if (XKUtils.isEmpty(ignoredNames) || !ignoredNames.contains(name)) {
Method keyMethod = tClass.getMethod(keyMethodName);
Method valMethod = tClass.getMethod(valueMethodName);
K key = (K) keyMethod.invoke(value);
V val = (V) valMethod.invoke(value);
map.put(key, val);
}
} catch (Exception exception) {
LOGGER.error(exception.getMessage());
}
}
return map;
}
/**
* 检索enumClass中的methodName输出为val值的枚举
* <br> 通过某个值获取枚举类
*
* @param enumClass 枚举类
* @param methodName 方法名
* @param val 值
* @param <T> 枚举类型
* @param <V> 值类型
* @return 具体的枚举类,为null是不存在
*/
public static <T extends Enum<T>, V> T getEnum(Class<T> enumClass, String methodName, V val) {
if (XKUtils.isEmpty(methodName) || null == val) {
return null;
}
T[] values = enumClass.getEnumConstants();
for (T value : values) {
try {
Class<?> tClass = value.getClass();
Method valMethod = tClass.getMethod(methodName);
if (valMethod.invoke(value).equals(val)) {
return value;
}
} catch (Exception exception) {
LOGGER.error(exception.getMessage());
}
}
return null;
}
/**
* 检索enumClass中的methodName是否有输出val值
* <br> 判断枚举是否存在某个值
*
* @param enumClass 枚举类
* @param methodName 方法名
* @param val 值
* @param <T> 枚举类型
* @param <V> 值类型
* @return true 存在 , false 不存在
*/
public static <T extends Enum<T>, V> boolean contains(Class<T> enumClass, String methodName, V val) {
return null != getEnum(enumClass, methodName, val);
}
}
- 看看怎么使用,不废话,直接上代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
/**
* 使用案例
*
* @author XXX
* @version 1.0
* @date 2025/6/25 18:14
*/
public class Test {
private static final Logger LOGGER = LoggerFactory.getLogger(Test.class);
public static void main(String[] args) {
// 生成枚举LIST
List<Map<String, Object>> list = EnumMethodUtil.toSelections(TaskExecuteStatus.class);
LOGGER.info("List = {} ", list);
// 生成枚举MAP
Map<String, Integer> map = EnumMethodUtil.toMap(TaskExecuteStatus.class);
LOGGER.info("Map = {} ", map);
// 获取指定值的 枚举
TaskExecuteStatus t = EnumMethodUtil.getEnum(TaskExecuteStatus.class, "getValue", 1);
LOGGER.info("TaskExecuteStatus label = '{}' , value = '{}'", t.getLabel(), t.getValue());
}
}
- 执行结果
小结:这个工具类有效的减少了去手动编写转换的代码,当然也还有别的办法,例如使用注解,或者使用三方库的序列化方法进行转换。
五、总结
哈哈,自夸一下,本篇文章真是干货满满!
咱先是探讨了枚举的定义和LabelValue形式的用途,接着又吐槽了手动转换枚举的种种痛点。最后,咱直接祭出了一个超实用的工具类,用反射的方式轻松解决了枚举转换的难题。
实际我的目的也是推广这个工具类,嘿嘿。
希望本文对您有所帮助。如果有任何错误或建议,请随时指正和提出。
同时,如果您觉得这篇文章有价值,请考虑点赞和收藏。这将激励我进一步改进和创作更多有用的内容。
感谢您的支持和理解!