Java的枚举相对于具有公共静态final字段的类有什么优势?
我对C#非常熟悉,但开始更多地使用Java。 我期望知道Java中的枚举与C#中的枚举基本相同,但显然情况并非如此。 最初我很高兴得知Java枚举可能包含多个似乎非常有利的数据片段( http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html )。 然而,从那以后,我发现了许多在C#中微不足道的function,比如能够轻松地为枚举元素指定一个特定的值,并因此能够将整数转换为枚举而无需花费大量精力即将整数值转换为匹配的Java Enum )。
所以我的问题是:有一堆公共静态最终字段的Java枚举有什么好处? 还是只是提供更紧凑的语法?
编辑:让我更清楚。 什么是Java枚举相对于一堆相同types的公共静态最终字段的好处? 例如,在第一个链接的行星例子中,使用这些公共常量的类的枚举有什么优势:
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);
据我所知,卡萨布兰卡的答案是唯一满足这一点的答案。
从技术上讲,人们确实可以将枚举视为一个具有一组types常量的类,事实上,枚举常量是如何在内部实现的。 然而,使用enum
为您提供了有用的方法( Enum javadoc ),否则您将不得不实现自己,例如Enum.valueOf
。
没有人提到在switch
语句中使用它们的能力; 我也会把它扔进去
这允许任意复杂的枚举以干净的方式使用,而不使用instanceof
, if
序列或非string/整数切换值可能会造成混淆。 典型的例子是一个状态机。
- types安全和价值安全。
- 保证单身。
- 能够定义和覆盖方法。
- 能够在没有资格的
case
在switch
语句case
语句中使用值。 - 通过
ordinal().
内置序列化值ordinal().
- 序列化的名称不是价值,它提供了一定程度的面向未来。
-
EnumSet
和EnumMap
类。
没有混乱。 以Font
为例。 它有一个构造函数,它取得你想要的Font
的名称,它的大小和样式( new Font(String, int, int)
)。 直到今天,我不记得是否风格或大小先行。 如果Font
使用了所有不同样式的enum
( PLAIN
, BOLD
, ITALIC
, BOLD_ITALIC
),它的构造函数看起来就像Font(String, Style, int)
,避免了混淆。 不幸的是,在创buildFont
类的时候, enum
并不在身边,而且由于Java必须保持反向兼容性,所以我们总是被这个含糊不清的东西所困扰。
当然,这只是使用enum
而不是public static final
常量的一个参数。 枚举也是完美的单身人士和实现默认行为,同时允许以后定制(IE的战略模式 )。 后者的一个例子是java.nio.file
的OpenOption
和StandardOpenOption
:如果开发者想要创build自己的非标准的OpenOption
,他可以。
主要优点是types安全。 使用一组常量,可以使用任何相同的内部types的值,引入错误。 使用枚举只能使用适用的值。
例如
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
public enum Size { SMALL, MEDIUM, LARGE }; public void setSize(Size s) { ... } obj.setSize( ? ); // Can't even express the above example with an enum
这里有很多好的答案,但没有一个提到专门针对枚举的Collection API类/接口的高度优化的实现 :
- EnumSet
- EnumMap的
这些枚举特定的类只接受Enum
实例( EnumMap
只接受Enum
作为关键字),并且只要有可能,它们就会在其实现中恢复到紧凑的表示和位操作。
这是什么意思?
如果我们的Enum
types没有更多的64个元素(大多数现实生活中的Enum
示例将符合这个要求),那么这些实现将这些元素存储在一个单独的long
值中,每个Enum
实例将被关联到这个64- long
。 将一个元素添加到EnumSet
只是简单地将适当的位设置为1,删除它只是将该位设置为0.testing一个元素是否在Set
中只是一个位掩码testing! 现在,你必须为此爱Enum
s!
你已经注意到,枚举的第一个好处是语法简单。 但是枚举的主要目的是提供一个众所周知的常量集合,默认情况下,它们形成一个范围,并通过types和值安全检查来帮助执行更全面的代码分析。
枚举的这些属性帮助程序员和编译器。 例如,假设您看到一个接受整数的函数。 那个整数是什么意思? 你可以传递什么样的价值? 你不会马上知道。 但是如果你看到一个接受枚举的函数,你就可以知道所有可能的值。
对于编译器来说,枚举有助于确定一系列的值,除非你为枚举成员指定了特殊的值,它们的范围是从0到0。 这有助于通过types安全检查等自动跟踪代码中的错误。 例如,编译器可能会警告你,你不能在你的switch语句中处理所有可能的枚举值(即,当你没有default
情况下,只处理N个枚举值中的一个)。 当你将一个任意的整数转换为枚举时,它也会发出警告,因为枚举的值范围小于整数,而这又可能触发不能真正接受整数的函数中的错误。 另外,当值从0开始时,为交换机生成跳转表变得更容易。
这不仅适用于Java,也适用于其他严格types检查的语言。 C,C ++,D,C#都是很好的例子。
例:
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) 没有types安全 :首先它不是types安全的; 你可以把任何有效的int值赋给int,例如99,尽pipe没有硬币来表示这个值。
2) 没有意义的打印 :任何这些常量的打印值将打印其数值,而不是有意义的硬币名称,例如,当您打印NICKLE时,将打印“5”而不是“NICKLE”
3) 没有命名空间 :要访问currencyDenom常量,我们需要给类名称加前缀,例如CurrencyDenom.PENNY,而不是只使用PENNY,虽然这也可以通过在JDK 1.5中使用静态导入来实现
枚举的优点
1)Java中的枚举是types安全的,并有自己的名字空间。 这意味着你的枚举在下面的例子中将有一个types,例如“货币”,你不能指定除了在枚举常量中指定的值。
public enum Currency {PENNY, NICKLE, DIME, QUARTER};
Currency coin = Currency.PENNY; coin = 1; //compilation error
2)Java中的Enum是像类或接口一样的引用types,您可以在java Enum中定义构造函数,方法和variables,这使得它比C和C ++中的Enum更强大,如Java Enumtypes的下一个示例所示。
3)您可以在创build时指定枚举常量的值,如下面的示例所示:public enum Currency {PENNY(1),NICKLE(5),DIME(10),QUARTER(25)}; 但是为了这个工作,你需要定义一个成员variables和一个构造函数,因为PENNY(1)实际上正在调用一个接受int值的构造函数,参见下面的例子。
public enum Currency { PENNY(1), NICKLE(5), DIME(10), QUARTER(25); private int value; private Currency(int value) { this.value = value; }
};
参考: http : //javarevisited.blogspot.in/2011/08/enum-in-java-example-tutorial.html
一个枚举是暗含最终,私有构造函数,它的所有值是相同的types或子types,您可以使用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.
枚举优点:
- 枚举是types安全的,静态字段不是
- 有一个有限数量的值(不可能传递不存在的枚举值,如果你有静态类字段,你可以犯这个错误)
- 每个枚举可以有多个属性(字段/ getters)分配 – 封装。 还有一些简单的方法: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;} }
“因此能够将一个整数转换为一个枚举没有一个体面的努力”添加一个方法转换为枚举枚举这样做。 只需添加包含映射的静态HashMap即可。
如果你真的想将ord = VAL_200.ordinal()转换回val_200,只需使用:EnumX.values()[ord]
当您使用枚举时,您可以编译时间检查有效值。 看看这个问题。
另一个重要的区别是,java编译器将基本types和string的 static final
字段视为文字。 这意味着这些常量变成内联。 它类似于C/C++
#define
预处理器。 看到这个问题 。 枚举不是这种情况。
枚举的最大优点是易于编写和线程安全:
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; }
更多
我认为一个enum
不能是final
,因为在编译器下编译器为每个enum
项生成子类。
更多信息来源
这通常被认为是不好的做法。 问题在于常数是实现类的公共“接口”(为了更好的词)。 这意味着实现类将所有这些值发布到外部类,即使它们只在内部需要。 常量遍及整个代码。 Swing中的SwingConstants接口就是一个例子,它由几十个类来实现,它们都将所有常量(甚至是不使用的)重新导出为自己的类。
常量接口模式是接口使用不佳。 一个类内部使用一些常量是一个实现细节。 实现一个常量接口会导致这个实现细节泄露到类的导出的API中。 对类的用户来说,类实现一个常量接口是没有意义的。 事实上,它甚至可能混淆它们。 更糟糕的是,它代表了一个承诺:如果在将来的发行版中,类被修改以便不再需要使用常量,它仍然必须实现接口以确保二进制兼容性。 如果一个非终结类实现了一个常量接口,那么它的所有子类的接口中的常量都会使其名称空间受到污染。
枚举可能是一个更好的方法。 或者你可以简单地把常量作为公共静态字段放在一个无法实例化的类中。 这允许另一个类访问它们而不会污染它自己的API。