我从未使用过枚举,在Java编程中已经有几年了。显然,它们已经发生了很大变化。现在它们甚至可以在自身内部进行完整的面向对象编程支持。
那么,我在日常编程中为什么要使用枚举?
当一个变量(尤其是方法参数)只能从一小组可能值中选择时,应始终使用枚举。例如,类型常量(合同状态:“permanent”、“temp”、“apprentice”)或标志(“立即执行”,“延迟执行”等)。
如果您使用枚举而不是整数(或字符串代码),则可以增加编译时检查并避免由于传递无效常量而导致的错误,并记录哪些值是合法的。
顺便提一下,过度使用枚举可能意味着您的方法功能过于复杂(通常最好拥有几个单独的方法,而不是一个方法接受几个标志来修改其操作方式),但如果必须使用标志或类型代码,则应使用枚举。
例如,哪个更好?
/** Counts number of foobangs.
* @param type Type of foobangs to count. Can be 1=green foobangs,
* 2=wrinkled foobangs, 3=sweet foobangs, 0=all types.
* @return number of foobangs of type
*/
public int countFoobangs(int type)
对比
/** Types of foobangs. */
public enum FB_TYPE {
GREEN, WRINKLED, SWEET,
/** special type for all types combined */
ALL;
}
/** Counts number of foobangs.
* @param type Type of foobangs to count
* @return number of foobangs of type
*/
public int countFoobangs(FB_TYPE type)
像这样的方法调用:
int sweetFoobangCount = countFoobangs(3);
然后变成:
int sweetFoobangCount = countFoobangs(FB_TYPE.SWEET);
在第二个示例中,可以立即了解允许使用哪些类型,文档和实现不能不同步,编译器可以强制执行此操作。同时,像下面这样的无效调用:int sweetFoobangCount = countFoobangs(99);
不再可能。
枚举花色 { 方块, 红桃, 梅花, 黑桃 }
- MC Emperorpublic class Color {
private Color() {} // Prevent others from making colors.
public static final Color RED = new Color();
public static final Color AMBER = new Color();
public static final Color GREEN = new Color();
}
Color trafficLightColor = Color.RED;
public enum Color { RED, AMBER, GREEN };
static final
枚举模拟并没有为您提供漂亮的 switch
case。对于枚举类型,Java switch 使用其变量类型来推断枚举 case 的范围,因此对于上面的 enum Color
,您只需要说:Color color = ... ;
switch (color) {
case RED:
...
break;
}
Color.RED
。如果您不使用枚举,那么使用具有switch
的命名数量的唯一方法类似于:public Class Color {
public static final int RED = 0;
public static final int AMBER = 1;
public static final int GREEN = 2;
}
int
。枚举和static final
的编译器检查已经消失了,这让我们很不开心。public class Color {
public static final int RED_TAG = 1;
public static final int AMBER_TAG = 2;
public static final int GREEN_TAG = 3;
public final int tag;
private Color(int tag) { this.tag = tag; }
public static final Color RED = new Color(RED_TAG);
public static final Color AMBER = new Color(AMBER_TAG);
public static final Color GREEN = new Color(GREEN_TAG);
}
Color color = ... ;
switch (color.tag) {
case Color.RED_TAG:
...
break;
}
public class SingletonClass {
public static final void INSTANCE = new SingletonClass();
private SingletonClass() {}
// all the methods and instance data for the class here
}
SingletonClass.INSTANCE
public enum SingletonClass {
INSTANCE;
// all the methods and instance data for the class here
}
线程安全
只有在单例使用惰性创建且没有锁时,线程安全才是一个潜在的问题。
public class SingletonClass {
private static SingletonClass INSTANCE;
private SingletonClass() {}
public SingletonClass getInstance() {
if (INSTANCE == null) INSTANCE = new SingletonClass();
return INSTANCE;
}
// all the methods and instance data for the class here
}
INSTANCE
仍为空的情况下同时调用getInstance
,则可能创建任意数量的实例。 这很糟糕。 唯一的解决方案是添加synchronized
访问以保护变量INSTANCE
。static final
代码没有这个问题。 它在类加载时急切地创建实例。 类加载是同步的。
enum
单例实际上是懒惰的,因为它直到第一次使用才会初始化。 Java初始化也是同步的,因此多个线程不能初始化多个INSTANCE
实例。 您将获得一个带有非常少代码的延迟初始化的单例。 唯一的负面影响是语法相当晦涩。 您需要知道这种习惯用法或深入了解类加载和初始化的工作原理才能知道发生了什么。static final
字段在类初始化时被初始化,而不是加载时。这与 enum
常量初始化完全相同(实际上,在底层它们是相同的)。这就是为什么即使在最初的 Java 版本中,尝试实现单例模式的“聪明”延迟初始化代码总是无意义的原因。 - Holgerenum StringComparator implements Comparator<String> {
NATURAL {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
},
REVERSE {
@Override
public int compare(String s1, String s2) {
return NATURAL.compare(s2, s1);
}
},
LENGTH {
@Override
public int compare(String s1, String s2) {
return new Integer(s1.length()).compareTo(s2.length());
}
};
}
CASE_INSENSITIVE { @Override public int compare(String s1, String s2) { return s1.compareToIgnoreCase(s2); } }
。另一个尚未提到的优点是您可以获得强大的序列化支持;持久性表单仅包含类名和常量名,不依赖于比较器的任何实现细节。 - Holgerenum.method()
执行原来在条件语句中执行的操作。相同的示例还显示了使用枚举的静态导入,产生更清晰的DSL代码。
一些其他有趣的特性包括枚举提供equals()
,toString()
和hashCode()
的实现,并实现Serializable
和Comparable
。
对于枚举所提供的所有内容的完整介绍,我强烈推荐Bruce Eckel的Java编程思想(第4版),该书专门介绍了这个主题的一个章节。尤其是涉及Rock、Paper、Scissors(即RoShamBo)游戏作为枚举的示例,非常有启发性。
来自Java文档-
每当需要表示一组固定的常量时,您应该使用枚举类型。这包括自然枚举类型,例如我们太阳系中的行星和数据集,在编译时,您知道所有可能的值 - 例如,菜单上的选择,命令行标志等。
一个常见的例子是用一个枚举类型替换一个类,其中包含一组私有静态final int常量(在合理数量的常量范围内)。基本上,如果您认为在编译时了解“某些东西”的所有可能值,则可以将其表示为枚举类型。枚举提供了比包含常量的类更好的可读性和灵活性。
我能想到的枚举类型的其他几个优点。枚举类始终只有一个实例(因此使用枚举作为单例模式的概念)。另一个优点是您可以将枚举用作switch-case语句中的类型。您还可以使用toString()在枚举上将它们打印为可读字符串。
DayOfWeek
枚举类型现在已经预定义,内置于 Java 8 及其后续版本中,作为 java.time 框架的一部分(并且已经被回溯到 Java 6 & 7 和 Android)。 - Basil BourqueLocale.setDefault(Locale.US)
比Locale.setDefault(1)
更易读,并通过在添加.
分隔符时显示IDE中的固定值集合来强制使用所有整数。为什么不使用String
或int
代替常量中的Enum
?
if
)来确保参数在有效范围内。String
(这取决于Enum
的复杂性)。此外,每个Enum
实例都是一个类,您可以定义其单独的行为。
了解到 enums
就像其他类一样拥有常量字段和一个私有构造函数是很有用的。
例如,
public enum Weekday
{
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
编译器将其编译如下;class Weekday extends Enum
{
public static final Weekday MONDAY = new Weekday( "MONDAY", 0 );
public static final Weekday TUESDAY = new Weekday( "TUESDAY ", 1 );
public static final Weekday WEDNESDAY= new Weekday( "WEDNESDAY", 2 );
public static final Weekday THURSDAY= new Weekday( "THURSDAY", 3 );
public static final Weekday FRIDAY= new Weekday( "FRIDAY", 4 );
public static final Weekday SATURDAY= new Weekday( "SATURDAY", 5 );
public static final Weekday SUNDAY= new Weekday( "SUNDAY", 6 );
private Weekday( String s, int i )
{
super( s, i );
}
// other methods...
}
枚举
表示枚举
,即逐个列举(一些事物)。
枚举
是包含固定常量集的数据类型。
或者
枚举
就像一个在编译时已知的固定实例集合的类
。
例如:
public class EnumExample {
interface SeasonInt {
String seasonDuration();
}
private enum Season implements SeasonInt {
// except the enum constants remaining code looks same as class
// enum constants are implicitly public static final we have used all caps to specify them like Constants in Java
WINTER(88, "DEC - FEB"), SPRING(92, "MAR - JUN"), SUMMER(91, "JUN - AUG"), FALL(90, "SEP - NOV");
private int days;
private String months;
Season(int days, String months) { // note: constructor is by default private
this.days = days;
this.months = months;
}
@Override
public String seasonDuration() {
return this+" -> "+this.days + "days, " + this.months+" months";
}
}
public static void main(String[] args) {
System.out.println(Season.SPRING.seasonDuration());
for (Season season : Season.values()){
System.out.println(season.seasonDuration());
}
}
}
枚举类型的优点:
详情请参见此链接。