如何在Java中将一个枚举类型转换为另一个枚举类型?

39

我有;

public enum Detailed {
    PASSED, INPROCESS, ERROR1, ERROR2, ERROR3;
}

并且需要将其转换为以下内容:

public enum Simple {
    DONE, RUNNING, ERROR;
}

首先,PASSEDDONEINPROCESSRUNNING,但所有错误都应为:ERROR。 显然可以为所有值编写用例,但可能有更好的解决方案?

7个回答

36

就我个人而言,我会创建一个Map<Detailed, Simple>并直接显式地进行操作,甚至可能使用switch语句。

另一种选择是将映射传递到构造函数中 - 当然,你只能按照一种方式进行:

public enum Detailed {
    PASSED(Simple.DONE),
    INPROCESS(Simple.RUNNING),
    ERROR1(Simple.ERROR),
    ERROR2(Simple.ERROR),
    ERROR3(Simple.ERROR);

    private final Simple simple;

    private Detailed(Simple simple) {
        this.simple = simple;
    }

    public Simple toSimple() {
        return simple;
    }
}

我发现这比Ted使用多态的方法更简单,因为我们不是真的在尝试提供不同的行为 - 只是一个不同的简单映射。

虽然你可能可以用序数值做一些聪明的事情,但它会更不明显,并且需要更多的代码 - 我认为没有任何好处。


在我看来,这更加简洁,现在如果你需要在多个地方使用映射,枚举本身就提供了它。 - b15

33

一种方法是在您的 Detailed 枚举中定义一个名为 asSimple() 的方法:

public enum Detailed {
    PASSED {
        @Override
        Simple asSimple() {
            return DONE;
        }
    },
    INPROCESS {
        @Override
        Simple asSimple() {
            return RUNNING;
        }
    },
    ERROR1,
    ERROR2,
    ERROR3;
    public Simple asSimple() {
        return Simple.ERROR; // default mapping
    }
}
您可以在需要进行映射时简单地调用该方法:
Detailed code = . . .
Simple simpleCode = code.asSimple();

这样做的优点是将与Detailed枚举(可能应归属于该枚举)相关的映射知识放在一起。它的缺点是将Simple的知识与Detailed的代码混合在一起。这可能是好事,也可能不是,这取决于您的系统架构。


4
我认为将映射逻辑放在一个枚举类型中以映射到另一个枚举类型会破坏封装性,引入耦合并降低内聚性。枚举类型的关注应该只是表示一组状态(或概念)。从一个枚举类型映射到另一个枚举类型的关注点应该被单独封装。 - Chomeh
3
我在回答中指出了Detailed需要了解Simple的知识。 (基本上,我表达了您在评论中所表达的担忧,但是用了“劣势”的含糊批评。您的表述更加精确。)尽管如此,我认为这种方法值得考虑。 OP的枚举名称表明Detailed在概念上是Simple的细化。如果是这样的话,那么DetailedSimple耦合并不是什么坏事(就像子类与父类耦合一样,这并不总是坏事)。 - Ted Hopp

11

使用EnumMap

通过实现转换服务,我将外部XML接口与内部领域模型解耦。这包括将JAXB生成代码中的枚举映射到领域模型枚举。

使用静态EnumMap将转换关注点封装在负责转换的类内。它具有内聚性。

@Service
public class XmlTransformer {

    private static final Map<demo.xml.Sense, Constraint.Sense> xmlSenseToSense;
    static {
        xmlSenseToSense = new EnumMap<demo.xml.Sense, Constraint.Sense> (
            demo.xml.Sense.class);
        xmlSenseToSense.put(demo.xml.planningInterval.Sense.EQUALS, 
            Constraint.Sense.EQUALS);
        xmlSenseToSense.put(demo.xml.planningInterval.Sense.GREATER_THAN_OR_EQUALS, 
            Constraint.Sense.GREATER_THAN_OR_EQUALS);
        xmlSenseToSense.put(demo.xml.planningInterval.Sense.LESS_THAN_OR_EQUALS, 
            Constraint.Sense.LESS_THAN_OR_EQUALS);
    }
    ...
}

2

Guava中的Enums.getIfPresent()关于Enum.name()

我们的情况是这种情况的一个特殊化。我们有两个Enum:一个用于应用程序,另一个用于核心库。核心库被少数应用程序使用,由不同的团队使用。每个应用程序查看整个功能的子集。为了开关、调节和选择策略等,使用枚举配置整个功能。

因此,我们最终得到:

  1. 一个库的枚举,包含所有可能的配置,可从应用程序中看到,还有一些特定于库的内容
  2. 每个应用程序一个枚举,包含与应用程序可以在库中看到/触摸的文字对应的文字,以及一些特定于应用程序的内容

然后,当我们向库传递数据时,我们调整所有数据以及那些配置。我们拥有所有的枚举,所以我们可以选择在不同的枚举中使用相同的文字来调用相同的配置。

Enum LibraryConfig {
    FUNCTION_ONE,
    FUNCTION_TWO,
    FUNCTION_THREE,
    FUNCTION_FOUR;
}

Enum Aplication1Config {
    FUNCTION_ONE,
    FUNCTION_TWO,
    FUNCTION_THREE,
    APPL1_FUNCTION_ONE,
    APPL1_FUNCTION_TWO;
}

Enum Aplication2Config {
    FUNCTION_ONE,
    FUNCTION_TWO,
    FUNCTION_FOUR;
    APPL2_FUNCTION_ONE;
}

当我们需要将一个类型转换为另一个类型时(从应用程序到库或从库到应用程序),我们使用com.google.common.base.Enums中的getIfPresent()方法,如下所示:

Original Answer翻译成"最初的回答"

Aplication1Config config1App1 = FUNCTION_TWO;
LibraryConfig configLib = Enums.getIfPresent(LibraryConfig.class, config1App1.name()).orNull();

我们检查configLib是否为null,以查看是否成功转换。我们使用这个最后一步是因为APPX_FUNCTION_YYY是应用特定的,并且用于在方向库- > 应用程序上进行转换,而不是传递配置值库特定(例如FUNCTION_FOUR)。

Maven的依赖管理:

以防万一有人需要:

    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>20.0</version>
    </dependency>

自制版本:

您可以使用Enum方法进行自己的转换,但需要注意异常以检测转换是否成功:

最初的回答

try {
    Aplication1Config config1App1 = FUNCTION_TWO;
    LibraryConfig configLib = LibraryConfig.valueOf(config1App1.name());
} catch (IllegalArgumentException iae) {
    // if the conversion did not succeed
}

1
泰德的回答非常Java式,但表达方式
passed == PASSED ? DONE : ERROR

也可以完成工作。


INPROCESS -> RUNNING 部分的映射出了什么问题? - Ted Hopp
@Ted:当然,你是对的,感谢你的纠正。我不会提出“PASSED?DONE:INPROCESS?RUNNING:ERROR”,因为显然这样写可读性较差。(话说回来,我以前见过这种写法,而且通过良好的格式化,它看起来并不奇怪...) - tiwo
1
@manuelvigarcia - 什么链接?还是你的评论应该针对tiwo? - Ted Hopp
@tiwo,链接无法打开。你可能需要找到另一个展示良好格式的网站……我对这个解决方案很感兴趣。 - manuelvigarcia

0
这是一个带有测试的简单枚举映射器:
-- 实现
-- 枚举
public enum FirstEnum {

A(0), B(1);

private final int value;

private FirstEnum(int value) {
    this.value = value;
}

public int getValue() {
    return value;
}
}

public enum  SecondEnum {

C(0), D(1);

private final int valueId;

private SecondEnum(int valueId) {
    this.valueId = valueId;
}

public int getValueId() {
    return valueId;
}

}

--映射器

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.Validate;

import com.google.common.collect.Sets;

public class EnumPropertyMapping {

private final Map<?, ?> firstMap;
private final Map<?, ?> secondMap;

private final Class<?> firstType;
private final Class<?> secondType;

private EnumPropertyMapping(
        Map<?, ?> firstMap, Map<?, ?> secondMap, Class<?> firstType, Class<?> secondType) {

    this.firstMap = firstMap;
    this.secondMap = secondMap;
    this.firstType = firstType;
    this.secondType = secondType;
}

public static Builder builder() {
    return new Builder();
}

@SuppressWarnings("unchecked")
public <R> R getCorrespondingEnum(Object mappedEnum) {
    Validate.notNull(mappedEnum, "Enum must not be NULL");
    Validate.isInstanceOf(Enum.class, mappedEnum, "Parameter must be an Enum");

    if (firstType.equals(mappedEnum.getClass())) {
        return (R) firstMap.get(mappedEnum);
    }

    if (secondType.equals(mappedEnum.getClass())) {
        return (R) secondMap.get(mappedEnum);
    }

    throw new IllegalArgumentException("Didn't found mapping for enum value: " + mappedEnum);
}

public static class Builder {

    private final Map<Object, Object> firstEnumMap = new HashMap<>();
    private final Map<Object, Object> secondEnumMap = new HashMap<>();
    private Class<?> firstEnumType;
    private Class<?> secondEnumType;

    public <T extends Enum<T>> Builder addFirst(Class<T> enumType, String propertyName) {
        firstEnumType = enumType;
        initMap(firstEnumMap, enumType.getEnumConstants(), propertyName);
        return this;
    }

    public <T extends Enum<T>> Builder addSecond(Class<T> enumType, String propertyName) {
        secondEnumType = enumType;
        initMap(secondEnumMap, enumType.getEnumConstants(), propertyName);
        return this;
    }

    private void initMap(Map<Object, Object> enumMap, Object[] enumConstants, String propertyName) {
        try {
            for (Object constant : enumConstants) {
                enumMap.put(PropertyUtils.getProperty(constant, propertyName), constant);
            }
        } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException ex) {
            throw new IllegalStateException(ex);
        }
    }

    public EnumPropertyMapping mapEnums() {
        Validate.isTrue(firstEnumMap.size() == secondEnumMap.size());
        Validate.isTrue(Sets.difference(firstEnumMap.keySet(), secondEnumMap.keySet()).isEmpty());

        Map<Object, Object> mapA = new HashMap<>();
        Map<Object, Object> mapB = new HashMap<>();

        for (Map.Entry<Object, Object> obj : firstEnumMap.entrySet()) {
            Object secondMapVal = secondEnumMap.get(obj.getKey());
            mapA.put(obj.getValue(), secondMapVal);
            mapB.put(secondMapVal, obj.getValue());
        }
        return new EnumPropertyMapping(mapA, mapB, firstEnumType, secondEnumType);
    }
}

}

-- 测试

import org.junit.Test;

import com.bondarenko.common.utils.lang.enums.FirstEnum;
import com.bondarenko.common.utils.lang.enums.SecondEnum;

import static junit.framework.TestCase.assertEquals;

public class EnumPropertyMappingTest {

@Test
public void testGetMappedEnum() {
    EnumPropertyMapping mapping = EnumPropertyMapping.builder()
                                                                                                     .addSecond(SecondEnum.class, "valueId")
                                                                                                     .addFirst(FirstEnum.class, "value")
                                                                                                     .mapEnums();

    assertEquals(SecondEnum.D, mapping.getCorrespondingEnum(FirstEnum.B));
    assertEquals(FirstEnum.A, mapping.getCorrespondingEnum(SecondEnum.C));
}

}

0
对我来说,这听起来更像是一个概念性问题而不是编程问题。为什么不只是删除“Simple”枚举类型,并在程序中所有地方使用另一个枚举类型呢?
再举个例子来更清楚地说明:你真的会尝试为一周的工作日(星期一到星期五)定义一个枚举类型,以及为一周的所有日子(星期一到星期日)定义另一个枚举类型吗?

由于在Java中,enum类型无法继承其他enum类型,因此您几乎每次都需要进行这种类型的映射。 - Ted Hopp
我可以想到一个应用程序——管理——在某些情况下,你希望第二天是星期一——下一个银行工作日——而在其他情况下是星期六——下一个“自然”日。例如:处理出纳员预约需要银行工作日,但计算休假天数时,需要计算自然日。一旦确定了这些需求,有必要在某个时候将它们对齐也不是不可想象的。 - manuelvigarcia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接