我非常熟悉 C#,但开始更多地使用 Java。我希望知道 Java 中的枚举基本上等同于 C# 中的枚举,但显然情况并非如此。最初,我很高兴得知 Java 枚举可以包含多条数据,这似乎非常有利 (http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html)。但是,从那以后,我发现 C# 中缺少许多微不足道的功能,例如能够轻松地为枚举元素分配某个值,因此无需付出大量努力就可以将整数转换为枚举(即Convert integer value to matching Java Enum)。
所以我的问题是:Java 枚举对具有一堆公共静态最终字段的类有什么好处吗?或者它只是提供更紧凑的语法?
编辑:让我更清楚。 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);
据我所知,卡萨布兰卡的答案是唯一满足这一点的答案。
public static final
字段,这些字段可能是类型值,不一定是 int
。
S
,所以现在我们讨论的是可能将它们传递给函数等。我们需要类型安全吗?嗯,不,但是大多数人认为“一切都是 void*
”的 c 风格类型是好的风格,它可以阻止错误(特别是如果传递多个枚举/字符串参数!)。它还将常量放入它们自己的命名空间等。与此相反,只有普通变量并没有真正的优势。
int
,没有类型安全,因为可以传递任何值。另一方面,类型化对象在类型安全方面与枚举没有什么不同。
类型安全和价值安全。保证单身。能够定义和覆盖方法。无需限定即可在 switch 语句 case 语句中使用值。通过 ordinal() 内置的值顺序化。按名称而不是按值进行序列化,这提供了一定程度的面向未来的能力。 EnumSet 和 EnumMap 类。
从技术上讲,确实可以将枚举视为具有一堆类型常量的类,这实际上是枚举常量在内部实现的方式。但是,使用 enum
会为您提供有用的方法 (Enum javadoc),否则您必须自己实现这些方法,例如 Enum.valueOf
。
.values()
用于迭代值列表。
没有人提到在 switch
语句中使用它们的能力;我也会把它扔进去。
这允许以干净的方式使用任意复杂的枚举,而无需使用 instanceof
、可能会混淆 if
序列或非字符串/int 切换值。典型的例子是状态机。
Planet
不是 enum
时如何切换 Planet
实例?
主要优点是类型安全。使用一组常量,可以使用相同内在类型的任何值,从而引入错误。对于枚举,只能使用适用的值。
例如
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
setSize(null)
,但它可能会比第一个示例的错误更快地失败。
更少的混乱。以 Font
为例。它有一个构造函数,它采用您想要的 Font
的名称、其大小和样式 (new Font(String, int, int)
)。直到今天,我都记不起风格或尺寸是第一位的。如果 Font
对其所有不同的样式(PLAIN
、BOLD
、ITALIC
、BOLD_ITALIC
)都使用了 enum
,则其构造函数看起来像 Font(String, Style, int)
,以防止任何混淆。不幸的是,在创建 Font
类时,还没有 enum
,而且由于 Java 必须保持反向兼容性,所以我们总是会受到这种模棱两可的困扰。
当然,这只是使用 enum
而不是 public static final
常量的一个参数。枚举也非常适合 singletons 并实现默认行为,同时允许以后进行自定义(即 strategy pattern)。后者的一个示例是 java.nio.file
的 OpenOption
和 StandardOpenOption
:如果开发人员想要创建自己的非标准 OpenOption
,他可以。
interface Foo { public void bar(String baz); }
。有人创建了一个调用 bar
: someFoo.bar(baz = "hello");
的类。我将 Foo::bar
的签名更改为 public void bar(String foobar)
。现在调用 someFoo
的人需要修改他们的代码,如果他们仍然希望它工作。
这里有很多很好的答案,但没有人提到专门针对枚举的 Collection API 类/接口的高度优化实现:
枚举集
枚举映射
这些枚举特定类仅接受 Enum
实例(EnumMap
仅接受 Enum
作为键),并且只要有可能,它们在其实现中恢复为紧凑表示和位操作。
这是什么意思?
如果我们的 Enum
类型的元素不超过 64 个(大多数现实生活中的 Enum
示例都符合此条件),则实现将元素存储在单个 long
值中,每个有问题的 Enum
实例将是与这个 64 位长 long
的一点相关联。将元素添加到 EnumSet
只是将正确的位设置为 1,删除它只是将该位设置为 0。测试元素是否在 Set
中只是一个位掩码测试!现在您一定会为此而爱上Enum
!
What does this mean?
部分学到了很多东西。我知道在大小 64 的实现上存在分歧,但我实际上并不知道为什么
EnumSet
对包含超过 64 个常量的类型也有相同的优化。它只需要使用 long[]
数组而不是单个 long
。虽然将 add
、remove
和 contains
操作减少为单位操作非常棒,但真正有趣的操作始于像 addAll
、removeAll
和 retainAll
这样的操作,它们不会逐位执行,而是一次处理 64 个元素(换句话说,通常是所有元素)。
例子:
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 value 的构造函数,请参见下面的示例。
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
正如您已经注意到的,枚举的第一个好处是语法简单。但是枚举的主要目的是提供一组众所周知的常量,默认情况下,它们形成一个范围,并通过类型和值安全检查帮助执行更全面的代码分析。
枚举的这些属性对程序员和编译器都有帮助。例如,假设您看到一个接受整数的函数。该整数可能意味着什么?你可以传递什么样的价值观?你真的不会马上知道。但是如果你看到一个接受枚举的函数,你就会非常清楚你可以传入的所有可能值。
对于编译器,枚举有助于确定值的范围,除非您为枚举成员分配特殊值,否则它们的范围从 0 到更高。这有助于通过类型安全检查等自动跟踪代码中的错误。例如,编译器可能会警告您,您没有在 switch 语句中处理所有可能的枚举值(即,当您没有 default
大小写并且只处理 N 个枚举值中的一个时)。当您将任意整数转换为枚举时,它还会警告您,因为枚举的值范围小于整数,这反过来可能会触发函数中不真正接受整数的错误。此外,当值为 0 及以上时,为开关生成跳转表变得更容易。
这不仅适用于 Java,对于其他具有严格类型检查的语言也是如此。 C、C++、D、C# 就是很好的例子。
枚举是隐式最终的,具有私有构造函数,它的所有值都是相同类型或子类型,您可以使用 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.
枚举好处:
枚举是类型安全的,静态字段不是 值的数量是有限的(不可能传递不存在的枚举值。如果你有静态类字段,你可能会犯这个错误) 每个枚举可以有多个属性(字段/吸气剂)分配 - 封装。还有一些简单的方法: 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 转换为枚举的方法。只需添加包含映射的静态 HashMap
如果您真的想将 ord=VAL_200.ordinal() 转换回 val_200 只需使用: EnumX.values()[ord]
最大的优点是枚举单例易于编写和线程安全:
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;
}
另一个重要区别是 java 编译器将 primitive types 和 String 的 static final
字段视为文字。这意味着这些常量变为内联的。它类似于 C/C++
#define
预处理器。请参阅this SO question。枚举不是这种情况。
枚举可以是本地的
从 Java 16 开始,可以在本地(在方法内)定义枚举。除了能够将枚举定义为嵌套或单独的类之外,此范围还可以。
这个新的本地定义范围与新的记录功能一起出现。有关详细信息,请参阅 JEP 395: Records。枚举、接口和记录都可以在 Java 16+ 中本地定义。
相反,public static final
字段始终具有全局范围。
static final
字段。它们的范围与本地 enum
的常量相同。但是,当您在实例方法中时,普通本地类仍然具有对周围 this
的隐式引用,这会阻止它们像 enum
一样使用。
我认为 enum
不能是 final
,因为在后台编译器会为每个 enum
条目生成子类。
来自 source 的更多信息
final
。否则,enum
类型本身将为 final
。
此处发布的枚举有很多优点,我现在正在按照问题中的要求创建这样的枚举。但我有一个包含 5-6 个字段的枚举。
enum Planet{
EARTH(1000000, 312312321,31232131, "some text", "", 12),
....
other planets
....
在这种情况下,当您在枚举中有多个字段时,很难理解哪个值属于哪个字段,因为您需要查看构造函数和眼球。
使用 static final
常量进行类并使用 Builder
模式创建此类对象使其更具可读性。但是,如果需要,您将失去使用枚举的所有其他优势。此类类的一个缺点是,您需要手动将 Planet
对象添加到 Planets.
的 list/set
我仍然更喜欢枚举而不是此类,因为 values()
派上用场,而且您永远不知道将来是否需要在 switch
或 EnumSet
或 EnumMap
中使用它们 :)
主要原因:枚举可帮助您编写结构良好的代码,其中参数的语义含义在编译时清晰且强类型 - 出于其他答案给出的所有原因。
Quid pro quo:在开箱即用的 Java 中,Enum 的成员数组是最终的。这通常很好,因为它有助于重视安全性和测试,但在某些情况下,它可能是一个缺点,例如,如果您可能从库中扩展现有的基本代码。相反,如果相同的数据位于具有静态字段的类中,您可以轻松地在运行时添加该类的新实例(您可能还需要编写代码将这些添加到该类的任何 Iterable 中)。但是 Enums 的这种行为可以改变:使用反射,您可以在运行时添加新成员或替换现有成员,尽管这可能只应在别无选择的特殊情况下完成:即它是一个 hacky 解决方案,可能会产生意想不到的问题,在 Can I add and remove elements of enumeration at runtime in Java 上查看我的答案。
你可以做 :
public enum Size { SMALL(1), MEDIUM(2), LARGE(3) };
private int sizeValue;
Size(sizeValue) {this.sizeValue = value; }
所以有了这个你可以得到像这样的大小值 SMALL.getSizeValue();
如果你想设置大小枚举不适合你,如果你只定义常量和固定值就可以了。
检查此link也许可以帮助您
switch
语句之一中,而这正是使用Enum
的最初动机。