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

新手上路,请多包涵

今天我浏览了这个网站上的一些问题,我发现了一个 enum 被用于单例模式,关于这种解决方案声称的线程安全优势。

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

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

原文由 MatBanik 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 338
2 个回答

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

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

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

举个例子,哪个更好?

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

不再可能。

原文由 sleske 发布,翻译遵循 CC BY-SA 3.0 许可协议

为什么要使用任何编程语言功能?我们拥有语言的原因是

  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),所以这是一个有价值的特性。

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

开关语句

上面的 static final 枚举模拟 没有 给你的一件事很好 switch 案例。对于枚举类型,Java 开关使用其变量的类型来推断枚举情况的范围,因此对于上面的 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
}

这给了我们同样的东西。我们可以避开这个问题,因为 Java 枚举 作为完整的类实现的,只是在顶部撒了一点语法糖。这又是一个较少的样板文件,但除非您熟悉这个成语,否则它并不明显。我也不喜欢你得到各种枚举函数,即使它们对单例没有多大意义: ordvalues 等(实际上有一个更棘手的模拟 Color extends Integer 可以与 switch 一起使用,但它非常棘手,它甚至更清楚地说明了为什么 enum 是一个更好的主意。)

线程安全

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

 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
}

如果许多线程同时调用 getInstanceINSTANCE 仍然为空,则可以创建任意数量的实例。这是不好的。唯一的解决方案是添加 synchronized 访问保护变量 INSTANCE

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

enum 单例实际上是惰性的,因为它直到第一次使用才被初始化。 Java 初始化也是同步的,因此多个线程不能初始化 INSTANCE 的多个实例。您将获得一个代码很少的惰性初始化单例。唯一的缺点是相当晦涩的语法。您需要知道这个惯用语或彻底了解类加载和初始化的工作原理才能知道发生了什么。

原文由 Gene 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题