什么是枚举,为什么它们有用?

2022-08-31 04:15:08

今天,我正在浏览此站点上的一些问题,我发现提到了在单例模式中使用,关于这种解决方案所谓的线程安全优势。enum

我从未使用过s,并且我已经用Java编程了好几年了。显然,他们改变了很多。现在,他们甚至在自己内部对OOP进行了全面的支持。enum

现在,为什么以及在日常编程中应该使用枚举?


答案 1

当变量(尤其是方法参数)只能从一小组可能值中获取一个时,应始终使用枚举。例如,类型常量(合同状态:“永久”、“临时”、“学徒”)或标志(“立即执行”、“延迟执行”)。等。

如果使用枚举而不是整数(或字符串代码),则可以增加编译时检查并避免错误传入无效常量,并记录哪些值是合法使用的。

顺便说一句,过度使用枚举可能意味着你的方法做得太多(通常最好有几个单独的方法,而不是一个方法需要几个标志来修改它的作用),但如果你必须使用标志或类型代码,枚举是要走的路。

例如,哪个更好?

/** 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);

不再可能。


答案 2

为什么使用任何编程语言功能?我们之所以有语言,是因为

  1. 程序员以计算机可以使用的形式高效,正确地表达算法。
  2. 维护者要理解别人编写的算法,并正确地进行更改。

枚举提高了正确性和可读性的可能性,而无需编写大量样板。如果你愿意写样板,那么你可以“模拟”枚举:

public 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 };

两者都提供来自编译器的相同级别的检查帮助。样板只是更多的类型。但是节省大量的输入使程序员更有效率(见1),所以这是一个有价值的功能。

这至少还有一个原因是值得的:

切换语句

上面的枚举模拟没有给你的一件事是好案例。对于枚举类型,Java 开关使用其变量的类型来推断枚举情况的范围,因此对于上述情况,您只需要说:static finalswitchenum Color

Color color = ... ;
switch (color) {
    case RED:
        ...
        break;
}

请注意,它不在案例中。如果不使用枚举,则使用命名数量的唯一方法是:Color.REDswitch

public Class Color {
    public static final int RED = 0;
    public static final int AMBER = 1;
    public static final int GREEN = 2;
}

但是现在,用于保存颜色的变量必须具有类型。枚举和模拟的漂亮编译器检查已经消失了。不开心。intstatic 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
}

这给了我们同样的东西。我们可以侥幸逃脱,因为Java枚举作为完整的类实现的,只有一点语法糖洒在顶部。这又是较少的样板,但除非你熟悉这个成语,否则它并不明显。我也不喜欢这样一个事实,即你会得到各种枚举函数,即使它们对单例没有多大意义:和,等等(实际上有一个更棘手的模拟,它将与switch一起使用,但它是如此棘手,以至于它更清楚地说明了为什么是一个更好的主意。ordvaluesColor extends Integerenum

螺纹安全

仅当在没有锁定的情况下懒惰地创建单例时,线程安全才是一个潜在的问题。

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
}

如果多个线程同时调用,而仍然为 null,则可以创建任意数量的实例。这很糟糕。唯一的解决方案是添加访问权限以保护变量。getInstanceINSTANCEsynchronizedINSTANCE

但是,上面的代码没有此问题。它在类加载时急切地创建实例。类装入已同步。static final

单例实际上是懒惰的,因为它在首次使用之前不会初始化。Java 初始化也是同步的,因此多个线程无法初始化 多个 实例。你得到一个懒惰初始化的单例,代码很少。唯一的缺点是相当晦涩的语法。您需要了解习语或彻底了解类加载和初始化的工作原理,以了解发生了什么。enumINSTANCE