与具有公共静态final字段的类相比,Java枚举有什么优势?


147

我对C#非常熟悉,但是开始在Java中工作更多。我希望了解到Java中的枚举基本上与C#中的枚举等效,但显然并非如此。最初,我很高兴得知Java枚举可以包含多条数据,这似乎非常有利(http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html)。但是,从那时起,我发现了很多缺少的功能,这些功能在C#中都是微不足道的,例如能够轻松地将枚举元素分配给某个值,从而无需花费大量精力即可将整数转换为枚举的功能(即转换整数值相匹配的Java枚举)。

所以我的问题是:与具有一堆公共静态final字段的类相比,Java枚举对Java枚举有什么好处吗?还是只是提供更紧凑的语法?

编辑:让我更清楚。与具有一堆相同类型的公共static final字段类相比,Java枚举有什么好处?例如,在第一个链接的行星示例中,枚举相对于具有这些公共常量的类的优势是什么:

public static final Planet MERCURY = new Planet(3.303e+23, 2.4397e6);
public static final Planet VENUS = new Planet(4.869e+24, 6.0518e6);
public static final Planet EARTH = new Planet(5.976e+24, 6.37814e6);
public static final Planet MARS = new Planet(6.421e+23, 3.3972e6);
public static final Planet JUPITER = new Planet(1.9e+27, 7.1492e7);
public static final Planet SATURN = new Planet(5.688e+26, 6.0268e7);
public static final Planet URANUS = new Planet(8.686e+25, 2.5559e7);
public static final Planet NEPTUNE = new Planet(1.024e+26, 2.4746e7);

据我所知,卡萨布兰卡的答案是唯一可以满足这一要求的答案。


4
@Bohemian:可能不是重复的,因为OP仅提到了public static final字段,这些字段可以是键入的值,不一定int是s。
casablanca 2012年

1
@Shahzeb几乎没有。显然,使用枚举而不是字符串常量是一个绝妙的好主意,而不是鼓励的。类型安全,不需要静态实用函数,等等。..绝对没有理由使用字符串。
Voo 2012年

1
@Voo是的,我知道会有分歧。这是49秒中的一个。枚举很棒(我很喜欢它们并且经常使用它们),但是在声明一个常量或使用它时,您需要什么类型的安全性。每次需要为String常量声明一个常量时,创建枚举是一个过大的杀伤力。
Shahzeb 2012年

4
@Shahzeb如果您有单个变量,请确保使用字符串,在这里不会发生太多(单个值作为参数是毫无意义的)。但是请注意,我们正在谈论的是常量S,因此现在我们正在谈论的是将它们传递给函数等。我们是否需要类型安全?好吧,不是,但是大多数人认为c风格的“一切都void*很好”,并且可以阻止错误(特别是如果传递多个enum / string参数!)。还将常量放入其自己的名称空间中,等等。与此相反,仅具有普通变量并没有真正的优势。
Voo 2012年

3
@波西米亚人:我不知道如何。使用ints时,没有类型安全性,因为可以传递任何值。另一方面,在类型安全方面,有类型的对象与枚举没有什么不同。
casablanca 2012年

Answers:


78

从技术上讲,确实可以将枚举视为具有一堆类型化常量的类,而实际上这就是在内部实现枚举常量的方式。enum但是,使用可以为您提供有用的方法(Enum javadoc),否则您将必须自己实现Enum.valueOf


14
.values()需要遍历值列表。
h3xStream 2012年

1
尽管这不是很令人满意,但这似乎是正确的答案。在我看来,对于Java而言,仅仅为了更紧凑的语法和对Enum方法的访问而添加对枚举的支持几乎是不值得的。
Craig W

7
@Craig您的直觉是正确的-这是一个非常糟糕的答案,因为它完全错过了枚举的目的。请参阅我在问题下的评论,以了解部分原因。
波希米亚

1
@波西米亚人:我没有“遗漏”枚举的目的,我一直都在使用它们。请参阅上面我对您的评论的回复。
casablanca 2012年

1
如果调用两次,Enum.valuesOf方法是否返回相同的对象?
EmreAktürk,2015年

104
  1. 类型安全和价值安全。
  2. 保证单身。
  3. 能够定义和覆盖方法。
  4. 能够在switch语句中使用值case无限制。
  5. 通过内置的值序列化 ordinal().
  6. 按名称序列化而不按值序列化,这提供了一定程度的前瞻性。
  7. EnumSetEnumMap课程。

19
说了这么多,每当我将代码放入枚举时,我都会后悔。
罗恩侯爵(Marques of Lorne)

4
你为什么后悔?我从来没有……
glglgl

2
@glglgl因为它将特定于应用程序的代码放到一个我认为它并不真正属于的地方,那实际上只是定义了一组值。如果我要再做一次,我将把它包含在众多switch陈述中,其中之一就是使用a的原始动机Enum
罗恩侯爵

72

没有人提到在switch语句中使用它们的能力。我也会把它扔进去。

这允许以干净的方式使用任意复杂的枚举,而无需使用instanceof,可能引起混淆的if序列或非字符串/整数切换值。典型的例子是状态机。


无论如何,您没有提到枚举与静态字段的任何好处,您可以在带有静态字段的switch语句中使用足够的类型。Op需要真正的功能或性能差异
Genaut

@Genaut的好处是枚举具有比字符串或整数更多的功能-问题在于我提供的差异。该任择议定书已经知道什么是枚举,而不是别人已经提到switch语句,当我发布这个4.5岁前,且至少有几个人发现它提供了新的信息_(ツ)_ /¯
戴夫·牛顿

44

主要优点是类型安全。通过一组常量,可以使用相同内在类型的任何值,从而引入错误。对于枚举,只能使用适用的值。

例如

public static final int SIZE_SMALL  = 1;
public static final int SIZE_MEDIUM = 2;
public static final int SIZE_LARGE  = 3;

public void setSize(int newSize) { ... }

obj.setSize(15); // Compiles but likely to fail later

public enum Size { SMALL, MEDIUM, LARGE };

public void setSize(Size s) { ... }

obj.setSize( ? ); // Can't even express the above example with an enum

3
类也是安全的类型...:/(假设静态字段是容器类的类型)
h3xStream 2012年

您仍然可以传递一个无效值,但这将是一个编译时错误,这很容易发现。
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz 2012年

2
您可以调用setSize(null)第二个示例,但是它可能比第一个示例的错误要早得多。
杰弗里

42

混乱较少。举个Font例子。它具有一个构造函数,该构造函数采用所需名称Font,大小和样式(new Font(String, int, int))。直到今天,我都不记得样式或尺寸是最重要的。如果Font已经使用了enum其所有不同的风格(PLAINBOLDITALICBOLD_ITALIC),它的构造看起来像Font(String, Style, int),防止任何混乱。不幸的是,创建类enum时并没有出现s Font,并且由于Java必须保持反向兼容性,所以我们总是会受到这种歧义的困扰。

当然,这只是使用常量enum而不是public static final常量的参数。枚举也非常适合单例和实现默认行为,同时允许以后进行自定义(例如策略模式)。后者的一个例子是java.nio.fileOpenOptionStandardOpenOption:如果开发者想创造自己的不规范OpenOption,他可能。


如果要求两个相同的枚举,则您的“字体”情况仍然不明确。解决该问题的标准答案是许多语言称为“ 命名参数”。更好的IDE方法签名支持。
aaaaaa 2015年

1
@aaaaaa我还没有看到很多情况下构造函数会在enum不使用varargs或a Set来接受任意数量的情况下接受两个相同的情况。
杰弗里

@aaaaaa命名参数的主要问题取决于实现细节(参数的名称)。我可以建立一个界面interface Foo { public void bar(String baz); }。有人创建了一个可以调用的类barsomeFoo.bar(baz = "hello");。我将的签名更改Foo::barpublic void bar(String foobar)。现在,打电话的人someFoo如果仍然希望它可以工作,就需要修改他们的代码。
杰弗里

我也不记得看到连续的枚举类型,但以为常见的枚举类型可能是DAY_OF_WEEK或类似名称。关于界面的好处-没想到。就我个人而言,我将解决由未命名参数引起的广泛歧义的问题,这需要更强大的IDE支持。我知道这是一个判断电话,并且需要认真考虑对API的重大更改。
aaaaaa

26

这里有很多好的答案,但是没有人提到有专门针对枚举的Collection API类/接口的高度优化的实现

这些枚举特定的类仅接受Enum实例(EnumMap仅接受Enums作为键),并且在可能的情况下,它们将在实现中恢复为紧凑表示形式和位操作。

这是什么意思?

如果我们的Enum类型不超过64个元素(大多数实际Enum示例都符合此条件),则实现将这些元素存储在单个long值中,每个Enum有问题的实例将与这64位long关联long。向an添加元素EnumSet只是将适当的位设置为1,将其删除只是将该位设置为0。测试元素是否在Set中只是一个位掩码测试!现在,您必须Enum为此而爱!


1
我以前知道这两个,但是我从您的What does this mean?部分中学到了很多。我知道大小为64的实施方案存在分歧,但我实际上并不知道为什么
Christopher Rucinski 2015年

15

例:

public class CurrencyDenom {
   public static final int PENNY = 1;
 public static final int NICKLE = 5;
 public static final int DIME = 10;
public static final int QUARTER = 25;}

Java常量的局限性

1)没有类型安全:首先,它不是类型安全的;您可以将任何有效的int值分配给int,例如99,尽管没有硬币可以表示该值。

2)没有意义的打印:任何一个常量的打印值都将打印其数值而不是硬币的有意义的名称,例如,当您打印NICKLE时,它将打印“ 5”而不是“ NICKLE”

3)没有命名空间:要访问currencyDenom常量,我们需要在类名前面加上CurrencyDenom.PENNY前缀,而不仅仅是使用PENNY,尽管这也可以通过在JDK 1.5中使用静态导入来实现

枚举的优点

1)Java中的枚举是类型安全的,并且具有自己的名称空间。这意味着您的枚举在下面的示例中将具有例如“货币”的类型,并且您不能分配除枚举常量中指定的值之外的任何值。

public enum Currency {PENNY, NICKLE, DIME, QUARTER};

Currency coin = Currency.PENNY; coin = 1; //compilation error

2)Java中的Enum是类或接口之类的引用类型,您可以在Java Enum中定义构造函数,方法和变量,这使其比C和C ++中的Enum更强大,如下面的Java Enum类型示例所示。

3)您可以在创建时指定枚举常量的值,如下例所示:public enum Currency {PENNY(1),NICKLE(5),DIME(10),QUARTER(25)}; 但这要工作,您需要定义一个成员变量和一个构造函数,因为PENNY(1)实际上正在调用一个接受int值的构造函数,请参见下面的示例。

public enum Currency {
    PENNY(1), NICKLE(5), DIME(10), QUARTER(25);
    private int value;

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

参考:https//javarevisited.blogspot.com/2011/08/enum-in-java-example-tutorial.html


11

您已经注意到,枚举的第一个好处是语法简单。但是枚举的主要目的是提供一组众所周知的常量,默认情况下,它们形成一个范围,并通过类型和值安全性检查来帮助执行更全面的代码分析。

枚举的那些属性对程序员和编译器都有帮助。例如,假设您看到一个接受整数的函数。该整数意味着什么?您可以传递什么样的价值观?您不是马上就知道。但是,如果您看到一个接受枚举的函数,那么您将非常清楚可以传入的所有可能值。

对于编译器,枚举可帮助确定值的范围,除非您为枚举成员分配特殊值,否则它们的取值范围为0至更高。这有助于通过类型安全检查等自动跟踪代码中的错误。例如,编译器可能警告您不要在switch语句中处理所有可能的枚举值(例如,当您没有default大小写并且仅处理N个枚举值中的一个时)。当您将任意整数转换为enum时,它还会警告您,因为enum的值范围小于integer的值范围,这反过来可能会触发实际上不接受整数的函数中的错误。同样,当值从0开始时,为开关生成跳转表变得更加容易。

这不仅适用于Java,而且适用于具有严格类型检查的其他语言。C,C ++,D,C#是很好的例子。


4

枚举是隐式最终的,使用私有构造函数,其所有值都是相同类型或子类型,可以使用values(),获取其name()或值来获取其所有值,ordinal()也可以按数字或名称查找枚举。

您还可以定义子类(即使从概念上讲是最终的,也无法通过其他任何方式来完成)

enum Runner implements Runnable {
    HI {
       public void run() {
           System.out.println("Hello");
       }
    }, BYE {
       public void run() {
           System.out.println("Sayonara");
       }
       public String toString() {
           return "good-bye";
       }
    }
 }

 class MYRunner extends Runner // won't compile.

4

枚举的好处:

  1. 枚举是类型安全的,静态字段不是
  2. 值的数量有限(无法传递不存在的枚举值。如果您具有静态类字段,则可能会犯此错误)
  3. 每个枚举可以分配多个属性(字段/获取器)-封装。还有一些简单的方法:YEAR.toSeconds()或类似方法。比较:Colors.RED.getHex()与Colors.toHex(Colors.RED)

“例如轻松为枚举元素分配特定值的能力”

enum EnumX{
  VAL_1(1),
  VAL_200(200);
  public final int certainValue;
  private X(int certainValue){this.certainValue = certainValue;}
}

“并且因此无需花费大量精力即可将整数转换为枚举的能力” 添加了一种将int转换为枚举的方法。只需添加包含映射java enum的静态HashMap <Integer,EnumX>即可。

如果您确实要将ord = VAL_200.ordinal()转换回val_200,请使用:EnumX.values()[ord]


3

另一个重要的区别是Java编译器将原始类型String的static final字段视为文字。这意味着这些常量将内联。它类似于预处理器。看到这个问题。枚举不是这种情况。C/C++ #define



2

枚举的最大优点是单例易于编写且线程安全:

public enum EasySingleton{
    INSTANCE;
}

/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
     private volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

两者都是相似的,并且通过实现

//readResolve to prevent another instance of Singleton
    private Object readResolve(){
        return INSTANCE;
    }

更多


0

我认为enum不能这样做final,因为在后台编译器会为每个enum条目生成子类。

来自源的更多信息


在内部,它们不是最终的,因为–正如您所说–可以在内部进行子类化。但是,a,您不能自己对它们进行子类化,例如,使用自己的值进行扩展。
glglgl

0

此处列出的枚举有很多优点,我现在正在按照问题的要求创建此类枚举。但是我有一个5-6个字段的枚举。

enum Planet{
EARTH(1000000, 312312321,31232131, "some text", "", 12),
....
other planets
....

在这种情况下,当枚举中包含多个字段时,很难理解哪个值属于哪个字段,因为您需要查看构造函数和注意事项。

带有static final常量的类以及使用Builder模式创建此类对象使其更具可读性。但是,如果需要,您将失去使用枚举的所有其他优点。这种类的一个缺点是,您需要将Planet对象手动添加到list/setPlanets.

我还是喜欢在枚举类等,因为values()就派上用场了,你永远不知道,如果你需要他们在使用switchEnumSetEnumMap在未来:)


0

主要原因:枚举可帮助您编写结构良好的代码,其中参数的语义含义在编译时清晰明了且具有强类型-出于其他答案的所有原因。

原告戒:在开箱即用的Java中,Enum的成员数组是最终的。通常这样做很好,因为它有助于评估安全性和测试价值,但是在某些情况下,这可能是一个缺点,例如,如果您要从库中扩展现有的基本代码。相反,如果相同数据位于具有静态字段的类中,则可以在运行时轻松地添加该类的新实例(您可能还需要编写代码以将其添加到该类的任何Iterable中)。但是Enums的这种行为可以改变:使用反射,您可以在运行时添加新成员或替换现有成员,尽管这可能仅应在没有其他选择的特殊情况下才能完成:即,这是一个棘手的解决方案,可能会产生意外问题,看到我的答案我可以在Java运行时添加和删除枚举元素吗

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.