如何在Java中定义重复的枚举常量?

11

我想定义一个枚举类型,其中包含两个“值”相同的常量。我称这两个常量为重复项。考虑以下示例:我想定义一个浏览器类型列表,并且我想同时拥有一个字面上为“IE”和“InternetExplorer”的常量,如下所示:

enum Browser {
    CHROME("chrome"),
    FIREFOX("firefox"),
    IE("ie"),
    INTERNETEXPLORER("ie");

    String type;
    Browser(String type) {
        this.type = type;
    }
}

然而,使用这种方法,以下代码将会失败:

Browser a = Browser.IE;
Browser b = Browser.INTERNETEXPLORER;
Assert.assertTrue(a==b);

我能想到的唯一解决方法是添加一个value()方法,它属于Browser类型,并返回浏览器实例的内部值。而等式测试代码将会是:

Assert.assertTrue(a.value()==b.value())

这不太好。那么有没有更好的想法呢?

为什么Java不允许覆盖Enum<T>类的equals()等方法?

编辑:

好的,感谢答案和评论。我同意我的原始想法与枚举的目的相违背。我认为以下更改可以满足我的需求。

public enum Browser {
   CHROME,
   FIREFOX,
   IE;

   public static Browser valueOfType(String type) {
       if (b == null) {
            throw new IllegalArgumentException("No browser of type " + type);
       switch (type.toLowerCase()) {   
          case "chrome":
               return Browser.CHROME;
          case "firefox":
               return Browser.FIREFOX;
          case "ie":
          case "internetexplorer":
          case "msie":
               return Browser.IE;
          default:
               throw new IllegalArgumentException("No browser of type " + type);
       }
   }
}

6
不确定我理解为什么您希望有2个枚举常量IEINTERNET_EXPLORER,如果它们是相同的。您能解释一下为什么需要这样吗? - assylias
3
枚举的目的是创建一组受控词汇,用于对相同概念进行分类或分组。你现在所做的打破了它的目的。 - gigadot
@assylias,我需要这样做是因为在某些不同的地方,我可能想要使用指向相同事物的不同标识符。也许另一个例子更有意义,有时我想使用ZERO,有时我想使用NULL,但我希望它们具有相同的含义。 - Ming Xue
@MingXue 我的观点是,这是对枚举类型的误用:如果 ZERO 和 NULL 代表同一件事,那么应该只有一个常量;如果它们不是同一件事,则应该有两个常量,并且它们不会相等。换句话说:重新考虑你的设计。 - assylias
为什么不只是使用一个包含唯一常量的enum,然后定义例如public static final Browser INTERNET_EXPLORER = Browser.IE - Louis Wasserman
@LouisWasserman 我认为你的方法是解决我的原始意图的有效方案。实际上,我认为这是“最好”的答案。然而,我现在认为这个意图本身并不理想,我们不应该向“枚举”用户公开歧义。 - Ming Xue
7个回答

11

在这种情况下,你可能需要使用层级枚举技巧。虽然它不能解决比较问题,但它为你的问题提供了一个非常好的替代方案。

http://java.dzone.com/articles/enum-tricks-hierarchical-data

我直接引用上述网站中的代码,稍作简化:

public enum OsType {
    OS(null),
        Windows(OS),
            WindowsNT(Windows),
                WindowsNTWorkstation(WindowsNT),
                WindowsNTServer(WindowsNT),
            WindowsXp(Windows),
            WindowsVista(Windows),
            Windows7(Windows),
        Unix(OS),
            Linux(Unix),
    ;
    private OsType parent = null;

    private OsType(OsType parent) {
        this.parent = parent;
    }
}

尽管在我看来,这个解决方案并没有帮助解决提问者的问题,但我还是点赞了,因为它涉及到了我的文章。 :) - AlexR

10
一种简单的方法是将实例变量放在枚举类内部。
enum Browser {
    CHROME,
    FIREFOX,
    INTERNETEXPLORER;
    public static final Browser IE=INTERNETEXPLORER;
}

然后,IE应该只是INTERNETEXPLORER的别名,您可以互换使用它们。

编辑:感谢big_m建议将IE设置为final

编辑2:这个技巧在大多数代码中都适用,但如果您正在使用switch/case,则有一个例外。以下是一个示例:

Browser b;
switch(b){
    case Browser.CHROME:
        //code for chrome
        break;
    case Browser.IE: // <---- SYNTAX ERROR, use Browser.INTERNETEXPLORER in this case
        //code for internet explorer
        break;
}

2
很棒的解决方案!不过我会将它设为 static final,这样就无法更改了。 - big_m

8
每个枚举相互扩展类Enum ,该类将equals() 定义为final 。这是因为枚举不是常规类。 JVM保证每个枚举元素都是唯一的,即在一个JVM中只存在每个元素的一个实例。

例如,在使用枚举时需要在switch语句中使用它们。

您正在尝试违反此概念:您想拥有两个相同的枚举成员。

但我可以向您提供其他解决方案:仅定义一个IE 成员。在枚举中定义String[] 成员和可以通过任何别名查找适当成员的方法:

public enum Browser {


    CHROME("Chrome"),
    FIREFOX("FireFox"),
    IE("IE", "MSIE", "Microsoft Internet Exporer"),
    ;

    private String[] aliases;

    private static Map<String, Browser> browsers = new HashMap<>();
    static {
        for (Browser b : Browser.values()) {
            for (String alias : b.aliases) {
                browsers.put(alias, b);
            }
        }
    }

    private Browser(String ... aliases) {
        this.aliases = aliases;
    }

    public static Browser valueOfByAlias(String alias) {
        Browser b = browsers.get(alias);
        if (b == null) {
            throw new IllegalArgumentException(
                    "No enum alias " + Browser.class.getCanonicalName() + "." + alias);
        }
        return b;
    }
}

2
你无法覆盖枚举的equals()方法,但即使你可以,==运算符也不会执行equals()方法:对于你的示例,没有办法使a == btrue
我能想到的最接近的方法是一个实用(静态)方法:
enum Browser {
    CHROME("chrome"),
    FIREFOX("firefox"),
    IE("ie"),
    INTERNETEXPLORER("ie");

    private final String type;
    Browser(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }

    public static boolean equals(Browser b1, Browser b2) {
        return b1.type.equals(b2.type);
    }
}

此外,请注意我会将type更改为private final。因为目前的情况下,你可以这样做:
IE.type = "netscape"; // would be allowed

0

Enum类中编写:

但是,扩展此类并不会使类成为枚举类型,因为编译器需要为其生成特殊信息。

为了支持基于引用的相等性,equals方法是final的。

您可以使用EnumSet创建相关枚举的EnumSet,然后可以使用contains方法。

public static EnumSet<Browser> MSE = EnumSet.of(Browser.IE,
    Browser.INTERNETEXPLORER);

所以你的代码将会类似以下内容。

public enum Browser {
CHROME("chrome"), FIREFOX("firefox"), IE("ie"), INTERNETEXPLORER("ie");

String type;

Browser(String type) {
    this.type = type;
}

public static EnumSet<Browser> MSE = EnumSet.of(Browser.IE,
        Browser.INTERNETEXPLORER);

public static void main(String[] args) {
    System.out.println(MSE.contains(Browser.IE));//true
    System.out.println(MSE.contains(Browser.INTERNETEXPLORER));//true
}
}

0
你真正应该做的是将输入转换为枚举进行归一化,即如果您的输入(来自用户/数据存储)是IE / INTERNETEXPLORER,则应将其解析为Browser.IE。这将消除代码中检查枚举是否为IE或INTERNETEXPLORER的大量冗余检查。

-2

我会移除字符串值和重复的IE实例,它们没有用处...

enum Browser {
    CHROME,
    FIREFOX,
    IE

如果你必须使用小写表示,只需在需要时从枚举名称进行转换即可。


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