在Java枚举中覆盖valueof()和toString()方法

150

我的enum中的值是需要包含空格的单词,但枚举类型的值不能有空格,所以它们都凑在一起了。我想覆盖toString()方法,在我指定的位置添加这些空格。

我还希望当我在同样添加了空格的字符串上使用valueOf()时,枚举类型能提供正确的枚举值。

例如:

public enum RandomEnum
{
     StartHere,
     StopHere
}

调用值为StartHereRandomEnumtoString()方法将返回字符串"Start Here"。在同一字符串"Start Here"上调用valueof()方法将返回枚举值StartHere

我该如何实现?


2
添加“Java”标签以获取更多评论/答案。 - Java42
7个回答

232

你可以尝试这段代码。由于你无法覆盖valueOf方法,因此你必须定义一个自定义方法(在下面的示例代码中为getEnum),该方法返回您需要的值,并更改客户端以使用此方法。

public enum RandomEnum {

    StartHere("Start Here"),
    StopHere("Stop Here");

    private String value;

    RandomEnum(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    @Override
    public String toString() {
        return this.getValue();
    }

    public static RandomEnum getEnum(String value) {
        for(RandomEnum v : values())
            if(v.getValue().equalsIgnoreCase(value)) return v;
        throw new IllegalArgumentException();
    }
}

4
如果你这样做:v.getValue().equals(value),那么getEnum就可以缩短,不需要进行空值检查。 - rtcarlson
您也可以懒惰地构建一个映射并重复使用它,但在构建时要注意线程问题。 - Maarten Bodewes
28
在无法将调用valueOf()方法更改为getValue()的情况下,不能在某些场景中正常工作,例如当您通过Hibernate注释定义特定字段映射到枚举值时。 - mmierins
在 Java 中枚举类型得到修复之前,@Bat 提供了一种更合适和面向对象的解决方案。name() 不应该是 final 的。 - Andrew T Finnell
可以使用“valueOf(RandomEnum.class, value)”代替“for”语句。 - Wojtek

23

尝试这个,但我不确定它能在任何地方都起作用 :)

public enum MyEnum {
    A("Start There"),
    B("Start Here");

    MyEnum(String name) {
        try {
            Field fieldName = getClass().getSuperclass().getDeclaredField("name");
            fieldName.setAccessible(true);
            fieldName.set(this, name);
            fieldName.setAccessible(false);
        } catch (Exception e) {}
    }
}

22
为什么只有我觉得那种东西看起来很邪恶?我的意思是,它看起来很酷,但如果一个没有上下文的人读了那段代码,他很可能会像“WTF?” - Nicolas Guillaume
18
那种代码如果被实现,就迫切需要一条注释来解释它为什么存在以及程序员试图解决的问题是什么。 :-) - Ti Strga
13
不要捕获通用异常,因为它可能是OutOfMemory或任何其他异常。 - crusy
5
这是一个糟糕的想法。最不好的是可能会发生无声错误,没有任何指示或恢复。现在“名称”值和代码中的符号值(例如A、B)是不同的。 +2 分聪明,-200 分非常聪明。我绝不会在代码审查中批准这个想法。 - pedorro
1
@pedorro,这似乎并不像你所说的那么糟糕。任何理解Java委员会所做的一切都不应该被视为真理的人都会在代码审查中通过这个问题。如果因为无法重写name()方法而不得不重新编写和维护所有枚举实用程序方法,那将是无穷无尽的痛苦。与其捕获并忽略异常,不如抛出RuntimeException,这样就可以解决问题了。 - Andrew T Finnell
显示剩余3条评论

18
您可以在枚举中使用静态地图将字符串映射到枚举常量。将其用于“getEnum”静态方法中。这样可以避免每次想要从其字符串值获取枚举时需要迭代枚举的需要。
public enum RandomEnum {

    StartHere("Start Here"),
    StopHere("Stop Here");

    private final String strVal;
    private RandomEnum(String strVal) {
        this.strVal = strVal;
    }

    public static RandomEnum getEnum(String strVal) {
        if(!strValMap.containsKey(strVal)) {
            throw new IllegalArgumentException("Unknown String Value: " + strVal);
        }
        return strValMap.get(strVal);
    }

    private static final Map<String, RandomEnum> strValMap;
    static {
        final Map<String, RandomEnum> tmpMap = Maps.newHashMap();
        for(final RandomEnum en : RandomEnum.values()) {
            tmpMap.put(en.strVal, en);
        }
        strValMap = ImmutableMap.copyOf(tmpMap);
    }

    @Override
    public String toString() {
        return strVal;
    }
}

请确保地图的静态初始化发生在枚举常量的声明之下。

顺便提一句 - 'ImmutableMap' 类型是来自谷歌的 guava API,我强烈推荐在这种情况下使用它。


编辑 - 根据评论:

  1. 此解决方案假定每个分配的字符串值都是唯一且非空的。考虑到枚举的创建者可以控制此内容,并且字符串对应于唯一且非空的枚举值,这似乎是一个安全的限制。
  2. 我按问题要求添加了 'toString()' 方法

1
我在这个答案上收到了几个踩的反对 - 有人愿意详细说明为什么这个答案不好吗?我只是好奇人们在想什么。 - pedorro
2
这种情况下会失败,如果枚举常量具有相同的“值”,例如RestartHere("replay"), ReplayHere("replay")或根本没有“值”,例如PauseHere, WaitHere(即枚举具有类似于private RandomEnum() { this.strVal = null; }的默认构造函数)。 - sactiw
1
你应该使用ImmutableMap.Builder来构建不可变映射,而不是创建MutableMap并使用ImmutableMap::copyOf。 - Avery Michelle Dawn
这看起来很有趣,但如果枚举只有几个不同的值,那么迭代它们不会花费太长时间。只有当枚举具有许多值时才值得这样做。 - Ben Thurley

17

Java 8 实现怎么样?(null 可以用您的默认枚举值替换)

public static RandomEnum getEnum(String value) {
    return Arrays.stream(RandomEnum.values()).filter(m -> m.value.equals(value)).findAny().orElse(null);
}

或者你可以使用:

...findAny().orElseThrow(NotFoundException::new);

2

我不认为你会成功使用valueOf("Start Here")。 但是关于空格...请尝试以下方法...

static private enum RandomEnum {
    R("Start There"), 
    G("Start Here"); 
    String value;
    RandomEnum(String s) {
        value = s;
    }
}

System.out.println(RandomEnum.G.value);
System.out.println(RandomEnum.valueOf("G").value);

Start Here
Start Here

2
以下是valueOf()的一个好的通用替代方案:
public static RandomEnum getEnum(String value) {
  for (RandomEnum re : RandomEnum.values()) {
    if (re.description.compareTo(value) == 0) {
      return re;
    }
  }
  throw new IllegalArgumentException("Invalid RandomEnum value: " + value);
}

使用compareTo()对于字符串来说比使用equals()更加优雅吗? - Betlista
优雅并不是问题 - 我同意使用 .equals() 也可以很好地工作。如果你喜欢,也可以使用 ==。 (尽管有一个很好的理由不这样做,那就是如果你将枚举替换为类时。) - exception
4
永远不要使用“==”来进行字符串比较,因为它会执行对象相等性检查,而你所需要的是内容相等性检查,为此请使用String类的equals()方法。 - sactiw
在Java 6+中不适用。 - Moshe Rabaev

2
你仍然可以在你的枚举中实现以下内容:

public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name){...}

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