从枚举到Label:一场代码世界的奇幻转换之旅

191 阅读7分钟

一、引言

枚举是我们在编码过程中是非常常用的一种类,特别是在一些类型,状态定义的时候,我们都建议使用枚举来定义和校验。

前面写过一篇文章介绍怎么实现枚举的校验,《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 复杂枚举接口定义的必要性

在编程中,通过接口定义枚举具有诸多优势,特别是在便于扩展和维护方面,

image.png

从上图可以看出,使用接口定义枚举数据类型,我们可以非常灵活的进行扩展,同样也可以一键进行枚举数据类型校验,而不用担心客户端版本不一致导致枚举数据类型的差异问题。

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());
    }

}
  • 执行结果

image.png

小结:这个工具类有效的减少了去手动编写转换的代码,当然也还有别的办法,例如使用注解,或者使用三方库的序列化方法进行转换。

五、总结

哈哈,自夸一下,本篇文章真是干货满满!

咱先是探讨了枚举的定义和LabelValue形式的用途,接着又吐槽了手动转换枚举的种种痛点。最后,咱直接祭出了一个超实用的工具类,用反射的方式轻松解决了枚举转换的难题。

实际我的目的也是推广这个工具类,嘿嘿。

希望本文对您有所帮助。如果有任何错误或建议,请随时指正和提出。

同时,如果您觉得这篇文章有价值,请考虑点赞和收藏。这将激励我进一步改进和创作更多有用的内容。

感谢您的支持和理解!