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

What's the advantage of a Java enum versus a class with public static final fields?

我对C语言非常熟悉,但开始更多的使用Java语言。我原以为Java中的枚举基本上等同于C语言,但是显然不是这样。最初,我很兴奋地知道Java枚举可以包含多条数据,这些数据看起来非常有利(http://DOCS.Oracle .COM/JavaSe/Trave/Java/javao/EnUn.html)。然而,从那时起,我发现在C语言中缺少许多琐碎的特征,例如能够容易地将枚举元素赋给某个值,并因此将整数转换为枚举而无需付出相当多的努力(即将整数值转换为匹配的JavaEnUM)。

所以我的问题是:在一组公共静态最终字段上,Java枚举有什么好处吗?或者它只是提供了更紧凑的语法?

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

1
2
3
4
5
6
7
8
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);

据我所知,卡萨布兰卡的回答是唯一能满足这一点的。


  • 类型安全和价值安全。
  • 保证单件。
  • 能够定义和重写方法。
  • 无条件使用switch语句case语句中的值的能力。
  • 通过ordinal().内置值序列
  • 按名称而不是按值序列化,这提供了一定程度的将来校对。
  • EnumSetEnumMap类。

  • 没有人提到在switch声明中使用它们的能力;我也会把它扔进去。

    这允许以干净的方式使用任意复杂的枚举,而不使用instanceof、可能混淆if序列或非string/int开关值。典型的例子是状态机。


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


    不那么混乱。以Font为例。它有一个构造函数,取您想要的Font的名称、大小和样式(new Font(String, int, int))。直到今天,我都不记得是款式还是尺寸排在第一位。如果Font使用了enum来表示它的所有不同样式(PLAINBOLDITALICBOLD_ITALIC,那么它的构造器看起来就像Font(String, Style, int),以防混淆。不幸的是,当EDCOX1×0创建类被创建时,EDCOX1的4个s不存在,并且由于Java必须保持反向兼容性,所以我们总是会被这种歧义所困扰。

    当然,这只是使用enum而不是public static final常量的一个论点。枚举也非常适合于单例和实现默认行为,同时允许以后的定制(即策略模式)。后者的一个例子是java.nio.fileOpenOptionStandardOpenOption:如果开发人员想要创建自己的非标准OpenOption,他可以。


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

    例如

    1
    2
    3
    4
    5
    6
    7
    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

    VS

    1
    2
    3
    4
    5
    public enum Size { SMALL, MEDIUM, LARGE };

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

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


    这里有许多很好的答案,但没有一个提到集合API类/接口的高度优化实现,专门针对枚举:

    • 枚举集
    • 枚举图

    这些枚举特定的类只接受enum实例(EnumMap只接受enum作为键),并且只要可能,它们在实现中都会恢复到紧凑的表示和位操作。

    这是什么意思?

    如果我们的enum类型没有更多的64个元素(大多数实际的enum示例都符合这个条件),那么实现将元素存储在单个long值中,每个enum实例将与这个64位长的long的一个位相关联。向EnumSet中添加一个元素,只需将适当的位设置为1,删除它只需将该位设置为0。如果一个元素在Set中,那么测试只是一个位掩码测试!现在你要为这个爱enum


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

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

    对于编译器,枚举有助于确定值的范围,除非为枚举成员指定特殊值,否则它们的范围为0到更大。这有助于通过类型安全检查等自动跟踪代码中的错误。例如,编译器可能会警告您,在switch语句中不处理所有可能的枚举值(即,当您没有default大小写,并且只处理n个枚举值中的一个)。当您将任意整数转换为枚举时,它也会警告您,因为枚举的值范围小于整数,从而可能触发函数中不真正接受整数的错误。此外,当值为0或更高时,为开关生成跳转表变得更容易。

    这不仅对Java来说是正确的,而且对于其他具有严格类型检查的语言也是如此。C、C++、D、C是很好的例子。


    例子:

    1
    2
    3
    4
    5
    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)无意义的打印:这些常量中的任何一个的打印值都将打印其数值而不是有意义的硬币名称,例如,当您打印镍币时,它将打印"5"而不是"镍币"。

    3)没有名称空间:要访问currencydenom常量,我们需要给类名加前缀,例如currencydenom.penny,而不是只使用penny,尽管这也可以通过在JDK 1.5中使用静态导入来实现。

    枚举的优点

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

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

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

    2)Java中的枚举是类或接口的引用类型,您可以在JavaEnm中定义构造函数、方法和变量,这使得它在C和C++中比EnUM强大,如下一个JavaEnm类型的示例所示。

    3)您可以在创建时指定枚举常量的值,如下例所示:公共枚举货币Penny(1)、Nickle(5)、Dime(10)、Quarter(25)但要实现这一点,您需要定义一个成员变量和一个构造函数,因为Penny(1)实际上正在调用一个接受int值的构造函数,请参见下面的示例。

    1
    2
    3
    4
    5
    6
    7
    8
    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


    枚举是隐含的最终的,有一个私有的构造函数,它的所有值都是相同的类型或子类型,您可以使用values()获取它的所有值,获取它的name()ordinal()值,或者您可以通过数字或名称查找枚举。

    您还可以定义子类(尽管概念上是最终的,但是您不能以任何其他方式进行定义)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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.

    枚举收益:

  • 枚举是类型安全的,静态字段不是
  • 值的数量有限(不可能传递不存在的枚举值)。如果您有静态类字段,则可能会犯此错误)
  • 每个枚举可以有多个分配的属性(字段/getter)-封装。还有一些简单的方法:year.toseconds()或类似的方法。比较:colors.red.gethex()与colors.tohex(colors.red)
  • "例如,能够轻松地为枚举元素分配某个值"

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

    "因此,能够将一个整数转换为一个枚举,而无需付出相当大的努力"添加一个将int转换为枚举的方法。只需添加包含映射的静态哈希映射。

    如果您真的想将ord=val_200.ordinal()转换回val_200,只需使用:enumx.values()[ord]


    另一个重要区别是Java编译器将EDCOX1的26个字段的基元类型和字符串视为文字。这意味着这些常量变成了内联的。类似于C/C++#define预处理器。看到这个问题。Enums不是这样的。


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

    1
    2
    3
    public enum EasySingleton{
        INSTANCE;
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * 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;
         }
    }

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

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

    更多


    当使用枚举时,您将获得有效值的编译时检查。看看这个问题。


    我认为enum不能是final,因为在hood下编译器为每个enum条目生成子类。

    来自来源的更多信息