ChatGPT解决这个技术问题 Extra ChatGPT

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

今天,我浏览了这个网站上的一些问题,发现提到了一个enum being used in singleton pattern,它声称这种解决方案的线程安全优势。

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

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

在他的书 Effective Java, Second Edition 中,Joshua Bloch 在 Item 3: Enforce the Singleton Property with a Private Constructor or an enum Type 中详细阐述了这种方法,在 Dr. Dobb's 中再版。
您不能实例化枚举,因为它具有私有构造函数,它们是在 jvm 启动时实例化的,因此它的单例枚举不是线程安全的。

S
Stephan

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

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

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

例如,哪个更好?

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

不再可能。


如果您使用枚举并希望允许组合值,请使用 EnumSet。这带有各种简洁的助手,例如 public static final EnumSet ALL = EnumSet.allOf(FB_TYPE.class);
这些答案是我真正喜欢在 SO 看到的答案,因为有人为此付出了真正的努力,干得好,我现在了解枚举!
我认为 you should *always* use enums when a variable can only take one out of a small set of possible values 不正确。使用常量值作为“二进制标志”(使用逻辑 or)对于内存不足的实时应用程序可能很有用。
@Elist 使用枚举可以提高代码的可读性,如果您在某些时候使用常量,您或您的继任者不知道为什么要使用它... :)
众所周知的例子:enum Suit { DIAMONDS, HEARTS, CLUBS, SPADES }
G
Gene

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

程序员以计算机可以使用的形式有效和正确地表达算法。维护人员了解其他人编写的算法并正确进行更改。

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

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 实例。你得到一个懒惰初始化的单例,代码很少。唯一的负面因素是相当晦涩的语法。您需要了解习语或彻底了解类加载和初始化的工作原理才能知道发生了什么。


最后一段真的为我澄清了单身人士的情况。谢谢!任何正在略读的读者都应该重新阅读。
我正在读这个,stackoverflow.com/questions/16771373/…。现在我对你的最后一段感到困惑。枚举如何不提供外层初始化功能?
static final 字段在类初始化时初始化,而不是在加载时。它与 enum 常量初始化完全相同(事实上,它们在底层是相同的)。这就是为什么尝试为单例实现“聪明的”惰性初始化代码总是毫无意义的,即使在第一个 Java 版本中也是如此。
C
Costi Ciudatu

除了已经提到的用例之外,我经常发现枚举对于实现策略模式很有用,遵循一些基本的 OOP 指南:

拥有数据所在的代码(即在枚举本身内 - 或者通常在枚举常量内,这可能会覆盖方法)。实现一个接口(或更多)以便不将客户端代码绑定到枚举(它应该只提供一组默认实现)。

最简单的示例是一组 Comparator 实现:

enum 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());
        }
    };
}

这种“模式”可以用于更复杂的场景,广泛使用枚举附带的所有好处:迭代实例,依赖它们的隐式顺序,通过名称检索实例,提供正确实例的静态方法对于特定的上下文等。而且您仍然将这一切隐藏在界面后面,因此您的代码无需修改即可使用自定义实现,以防您想要“默认选项”中不可用的东西。

我已经看到这成功地应用于建模时间粒度(每天、每周等)的概念,其中所有逻辑都封装在一个枚举中(为给定的时间范围选择正确的粒度,绑定到每个粒度的特定行为作为常数方法等)。而且,服务层看到的 Granularity 只是一个接口。


您也可以添加 CASE_INSENSITIVE { @Override public int compare(String s1, String s2) { return s1.compareToIgnoreCase(s2); }。尚未提及的一个优点是您获得了强大的序列化支持;持久形式只包含类名和常量名,不依赖于比较器的任何实现细节。
o
orangepips

其他答案都没有涵盖使枚举特别强大的东西是拥有template methods的能力。方法可以是基本枚举的一部分,并被每种类型覆盖。而且,通过附加到枚举的行为,它通常消除了对 if-else 构造或 switch 语句的需要,因为 blog post demonstrates - 其中 enum.method() 执行最初将在条件内执行的操作。同一个例子还展示了静态导入与枚举的使用以及生成更清晰的 DSL 类代码。

其他一些有趣的特性包括枚举为 equals()toString()hashCode() 提供实现并实现 SerializableComparable

对于枚举必须提供的所有内容的完整概述,我强烈推荐 Bruce Eckel 的 Thinking in Java 4th edition,它用一整章来讨论这个主题。特别有启发性的是涉及作为枚举的 Rock、Paper、Scissors(即 RoShamBo)游戏的示例。


C
CoolBeans

从 Java documents -

您应该在需要表示一组固定常量的任何时候使用枚举类型。这包括自然枚举类型,例如我们太阳系中的行星和您在编译时知道所有可能值的数据集——例如,菜单上的选择、命令行标志等。

一个常见的例子是用枚举类型的一组私有静态 final int 常量(在合理数量的常量内)替换一个类。基本上,如果您认为您在编译时知道“某物”的所有可能值,则可以将其表示为枚举类型。枚举为具有常量的类提供了可读性和灵活性。

我能想到枚举类型的其他优点很少。它们始终是特定枚举类的一个实例(因此出现了将枚举用作单例的概念)。另一个优点是您可以在 switch-case 语句中使用枚举作为类型。您也可以在枚举上使用 toString() 将它们打印为可读字符串。


您会在一年中的所有 366 天都使用枚举吗?它是一组固定的 (366) 常量(天数不变)。所以这表明你应该这样做。 :-/ 我会限制固定集的大小。
@marcog 通过一些聪明的思考,可以在一个紧凑的枚举中总结一周中的几天和每个月的几天。
说到一周中的几天...... DayOfWeek 枚举现在是预定义的,内置在 Java 8 和更高版本中,作为 java.time 框架(以及 back-ported to Java 6 & 7to Android)的一部分。
a
arnt

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

您可以使用 Enum 来表示较小的固定常量集或内部类模式,同时提高可读性。此外,枚举在方法参数中使用时可以强制执行一定的刚性。它们提供了将信息传递给 Planets example on Oracle's site 中的构造函数的有趣可能性,并且正如您所发现的,它们还提供了一种创建单例模式的简单方法。

例如:当您添加 . 分隔符而不是所有整数时,Locale.setDefault(Locale.US)Locale.setDefault(1) 更好地读取并强制使用 IDE 中显示的固定值集。


a
afsantos

Enum 以自记录方式枚举一组固定的值。
它们使您的代码更明确,也更不容易出错。

为什么不使用 Stringint 而不是 Enum 作为常量?

编译器不允许拼写错误,也不允许超出固定集合的值,因为枚举本身就是类型。结果:您不必编写前置条件(或手动 if)来确保您的论点在有效范围内。类型不变量是免费提供的。枚举可以有行为,就像任何其他类一样。无论如何,您可能需要类似数量的内存来使用字符串(这取决于枚举的复杂性)。

此外,Enum 的每个实例都是一个类,您可以为它定义其单独的行为。

另外,它们在创建实例时确保线程安全(当枚举被加载时),这在简化单例模式方面得到了很好的应用。

This blog 说明了它的一些应用程序,例如用于解析器的状态机


P
Premraj

enum 表示枚举eration,即一一提及(许多事物)。

枚举是包含一组固定常量的数据类型。

或者

枚举就像一个类,具有一组在编译时已知的固定实例。

例如:

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());
        }

    }
}

枚举的优点:

enum 提高了编译时检查的类型安全性,以避免在运行时出错。

enum 可以很容易地在 switch 中使用

枚举可以遍历

枚举可以有字段、构造函数和方法

enum 可以实现许多接口,但不能扩展任何类,因为它在内部扩展了 Enum 类

more


A
Ad Infinitum

知道 enums 与其他具有 Constant 字段和 private constructor 的类一样有用。

例如,

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...
}

P
Patrick Mevzek

什么是枚举

enum 是为枚举定义的一个新数据类型的关键字。应该自由地使用类型安全的枚举。特别是,它们是更老的 API 中用于表示相关项目集的简单 String 或 int 常量的强大替代方案。

为什么要使用枚举

枚举是 java.lang.Enum 的隐式最终子类

如果枚举是类的成员,则它是隐式静态的

new 永远不能与枚举一起使用,即使在枚举类型本身中也是如此

name 和 valueOf 仅使用枚举常量的文本,而 toString 可以被覆盖以提供任何内容,如果需要

对于枚举常量,equals 和 == 表示相同的内容,并且可以互换使用

枚举常量是隐式的 public static final

笔记

枚举不能扩展任何类。

枚举不能是超类。

枚举常量的出现顺序称为“自然顺序”,它也定义了其他项使用的顺序:compareTo、值的迭代顺序、EnumSet、EnumSet.range。

枚举可以有构造函数、静态和实例块、变量和方法,但不能有抽象方法。


您没有解释为什么要使用枚举。您只需在不相关的标题下列出枚举的几个属性。
@DuncanJones 条款不是最终的,静态的给出使用枚举的目的吗?
s
sErVerdevIL

除了其他人所说的之外。在我曾经工作过的一个较旧的项目中,实体(独立应用程序)之间的大量通信使用代表一个小集合的整数。使用静态方法将集合声明为 enum 以从 value 获取 enum 对象非常有用,反之亦然。代码看起来更干净,切换案例可用性和更容易写入日志。

enum ProtocolType {
    TCP_IP (1, "Transmission Control Protocol"), 
    IP (2, "Internet Protocol"), 
    UDP (3, "User Datagram Protocol");

    public int code;
    public String name;

    private ProtocolType(int code, String name) {
        this.code = code;
        this.name = name;
    }

    public static ProtocolType fromInt(int code) {
    switch(code) {
    case 1:
        return TCP_IP;
    case 2:
        return IP;
    case 3:
        return UDP;
    }

    // we had some exception handling for this
    // as the contract for these was between 2 independent applications
    // liable to change between versions (mostly adding new stuff)
    // but keeping it simple here.
    return null;
    }
}

使用 ProtocolType.fromInt(2) 从收到的值(例如 1,2)创建 enum 对象 使用 myEnumObj.name 写入日志

希望这可以帮助。


L
Leif Gruenwoldt

Enum 继承了 Object 类和抽象类 Enum 的所有方法。因此,您可以使用它的方法进行反射、多线程、序列化、可比较等。如果您只是声明一个静态常量而不是 Enum,则不能。除此之外,Enum 的值也可以传递给 DAO 层。

这是一个示例程序来演示。

public enum State {

    Start("1"),
    Wait("1"),
    Notify("2"),
    NotifyAll("3"),
    Run("4"),
    SystemInatilize("5"),
    VendorInatilize("6"),
    test,
    FrameworkInatilize("7");

    public static State getState(String value) {
        return State.Wait;
    }

    private String value;
    State test;

    private State(String value) {
        this.value = value;
    }

    private State() {
    }

    public String getValue() {
        return value;
    }

    public void setCurrentState(State currentState) {
        test = currentState;
    }

    public boolean isNotify() {
        return this.equals(Notify);
    }
}

public class EnumTest {

    State test;

    public void setCurrentState(State currentState) {
        test = currentState;
    }

    public State getCurrentState() {
        return test;
    }

    public static void main(String[] args) {
        System.out.println(State.test);
        System.out.println(State.FrameworkInatilize);
        EnumTest test=new EnumTest();
        test.setCurrentState(State.Notify);
        test. stateSwitch();
    }

    public void stateSwitch() {
        switch (getCurrentState()) {
        case Notify:
            System.out.println("Notify");
            System.out.println(test.isNotify());
            break;
        default:
            break;
        }
    }
}

C
Christophe Roussy

将枚举用于 TYPE SAFETY,这是一种语言功能,因此您通常会得到:

编译器支持(立即查看类型问题)

IDE 中的工具支持(开关情况下的自动完成、缺少的情况、强制默认值,...)

在某些情况下,枚举性能也很棒(EnumSet,类型安全替代传统的基于 int 的“位标志”。)

枚举可以有方法、构造函数,你甚至可以在枚举中使用枚举并将枚举与接口结合起来。

将枚举视为替换一组明确定义的 int 常量(Java 从 C/C++“继承”)并在某些情况下替换位标志的类型。

Effective Java 2nd Edition 一书有一整章介绍了它们,并详细介绍了它们。另见this Stack Overflow post


S
Steve

ENum 代表“枚举类型”。它是一种数据类型,具有您自己定义的一组固定常量。


您还没有解释为什么要使用枚举。
是的,我做到了。您可以使用它来存储您自己定义的一组固定常量。它的具体应用由你决定。没有关于何时、为什么或如何使用某物的说法。这取决于开发人员的需求。您只需要了解它是什么以及它是如何工作的。
R
Rafa

在我看来,你现在得到的所有答案都是有效的,但根据我的经验,我会用几句话来表达:

如果您希望编译器检查标识符值的有效性,请使用枚举。

否则,您可以像往常一样使用字符串(可能您为您的应用程序定义了一些“约定”)并且您将非常灵活......但是您不会获得 100% 的安全性来防止您的字符串上的拼写错误,您只会意识到它们在运行时。


M
Mark Chackerian

Java 允许您将变量限制为仅具有几个预定义值之一 - 换句话说,枚举列表中的一个值。使用 enums 有助于减少代码中的错误。以下是类外的 enums 示例:

enums coffeesize{BIG , HUGE , OVERWHELMING }; 
//This semicolon is optional.

这将 coffeesize 限制为具有: BIGHUGEOVERWHELMING 作为变量。


Y
Yoon5oo

枚举?为什么要使用它?我认为当您使用它时会更容易理解。我有同样的经历。

假设您有一个创建、删除、编辑和读取数据库操作。

现在,如果您将枚举创建为操作:

public enum operation {
    create("1")
    delete("2")
    edit("3")
    read("4")

    // You may have is methods here
    public boolean isCreate() {
        return this.equals(create);
    }
    // More methods like the above can be written

}

现在,您可以声明如下内容:

private operation currentOperation;

// And assign the value for it 
currentOperation = operation.create

因此,您可以通过多种方式使用它。对特定事物进行枚举总是好的,因为上面示例中的数据库操作可以通过检查 currentOperation 来控制。也许有人可以说这也可以通过变量和整数值来完成。但我相信 Enum 是一种更安全且适合程序员的方式。

另一件事:我认为每个程序员都喜欢布尔值,不是吗?因为它只能存储两个值,两个特定的值。因此,可以将 Enum 视为具有相同类型的设施,用户将在其中定义它将存储的值的数量和类型,只是方式略有不同。 :)


h
happybuddha

到目前为止,我从来不需要使用枚举。自从它们在 1.5 或版本 Tiger 中被引入以来,我一直在阅读它们,因为它在当天被召回。他们从来没有真正为我解决过“问题”。对于那些使用它的人(我看到他们中的很多人都这样做),我相信它肯定有一些用途。只是我的 2 英镑。


C
Community

这里有很多答案,只想指出两个具体的:

1) 在 Switch-case 语句中使用 as 常量。 Switch case 不允许您将 String 对象用于 case。枚举派上用场。更多:http://www.javabeat.net/2009/02/how-to-use-enum-in-switch/

2) 实施 Singleton Design Pattern - 再次枚举,来救援。用法,此处:What is the best approach for using an Enum as a singleton in Java?


d
djangofan

让我惊叹的是这个认识:Enum 有一个私有构造函数,只能通过公共枚举访问:

enum RGB {
    RED("Red"), GREEN("Green"), BLUE("Blue");

    public static final String PREFIX = "color ";

    public String getRGBString() {
        return PREFIX + color;
    }

    String color;

    RGB(String color) {
        this.color = color;
    }
}

public class HelloWorld {
    public static void main(String[] args) {
        String c = RGB.RED.getRGBString();
        System.out.print("Hello " + c);
    }
}

C
CodeToLife

至于我让代码在未来可读,最有用的可应用枚举案例在下一个片段中表示:

public enum Items {
    MESSAGES, CHATS, CITY_ONLINE, FRIENDS, PROFILE, SETTINGS, PEOPLE_SEARCH, CREATE_CHAT
}

@Override
public boolean onCreateOptionsMenu(Menu menuPrm) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menuPrm);
    View itemChooserLcl;
    for (int i = 0; i < menuPrm.size(); i++) {
        MenuItem itemLcl  = menuPrm.getItem(i);
            itemChooserLcl = itemLcl.getActionView();
            if (itemChooserLcl != null) {
                 //here Im marking each View' tag by enume values:
                itemChooserLcl.setTag(Items.values()[i]);
                itemChooserLcl.setOnClickListener(drawerMenuListener);
            }
        }
    return true;
}
private View.OnClickListener drawerMenuListener=new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Items tagLcl= (Items) v.getTag();
        switch (tagLcl){
            case MESSAGES: ;
            break;
            case CHATS : ;
            break;
            case CITY_ONLINE : ;
            break;
            case FRIENDS : ;
            break;
            case  PROFILE: ;
            break;
            case  SETTINGS: ;
            break;
            case  PEOPLE_SEARCH: ;
            break;
            case  CREATE_CHAT: ;
            break;
        }
    }
};

B
BradB

根据我的经验,我看到 Enum 的使用有时会导致系统很难更改。如果您将 Enum 用于一组经常更改的特定于域的值,并且它有许多其他依赖于它的类和组件,您可能需要考虑不使用 Enum。

例如,使用 Enum 进行市场/交易所的交易系统。那里有很多市场,几乎可以肯定会有很多子系统需要访问这个市场列表。每次您想要将一个新市场添加到您的系统中,或者如果您想要删除一个市场时,可能需要重新构建和发布阳光下的所有内容。

一个更好的例子是产品类别类型。假设您的软件管理百货公司的库存。有很多产品类别,以及此类别列表可能发生变化的许多原因。经理们可能想要储备新的产品线,摆脱其他产品线,并且可能不时重组类别。如果您仅仅因为用户想要添加产品类别而必须重建和重新部署所有系统,那么您已经采取了一些应该简单快速的方法(添加类别),并且变得非常困难和缓慢。

最重要的是,如果您所代表的数据随着时间的推移非常静态并且具有有限数量的依赖关系,则枚举是好的。但是如果数据变化很大并且有很多依赖关系,那么你需要一些在编译时不检查的动态(比如数据库表)。


O
Oleg Poltoratskii

基于枚举的单例

对老问题的现代看法

这种方法通过利用 Java 的保证来实现单例,即任何枚举值在 Java 程序中仅实例化一次,并且枚举为线程安全提供隐式支持。由于 Java 枚举值是全局可访问的,因此它们可以用作单例。

public enum Singleton {
    SINGLETON; 
    public void method() { }
}

这是如何运作的?好吧,代码的第二行可能被认为是这样的:

public final static Singleton SINGLETON = new Singleton(); 

我们得到了很好的早期初始化单例。

请记住,由于这是一个枚举,您始终可以通过 Singleton. SINGLETON 访问该实例:

Singleton s = Singleton.SINGLETON;

优点

为了防止在反序列化期间创建其他单例实例,请使用基于枚举的单例,因为枚举的序列化由 JVM 负责。枚举序列化和反序列化的工作方式与普通 java 对象不同。唯一被序列化的是枚举值的名称。在反序列化过程中,使用 enum valueOf 方法与反序列化的名称一起获取所需的实例。

基于枚举的单例允许保护自己免受反射攻击。 enum 类型实际上扩展了 java Enum 类。反射不能用于实例化 enum 类型的对象的原因是 java 规范不允许,并且该规则被编码在 Constructor 类的 newInstance 方法的实现中,该方法通常用于通过反射创建对象:

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
    throw new IllegalArgumentException("Cannot reflectively create enum objects");

Enum 不应该被克隆,因为每个值必须只有一个实例。

所有单例实现中最简洁的代码。

缺点

基于枚举的单例不允许延迟初始化。

如果您更改了设计并想将单例转换为多例,枚举将不允许这样做。 multiton 模式用于多个实例的受控创建,它通过使用映射进行管理。与每个应用程序(例如 java.lang.Runtime)只有一个实例不同,multiton 模式确保每个键都有一个实例。

Enum 仅出现在 Java 5 中,因此您不能在以前的版本中使用它。

单例模式有多种实现方式,每一种都各有利弊。

急切加载单例

双重检查锁定单例

按需初始化持有者成语

基于枚举的单例

他们每个人的详细描述都太冗长了,所以我只放了一篇好文章的链接 - All you want to know about Singleton


T
TEH EMPRAH

如果实现了某些方法,我会使用枚举作为有用的映射工具,避免使用多个 if-else

public enum Mapping {

    ONE("1"),
    TWO("2");

    private String label;

    private Mapping(String label){
        this.label = label;
    }

    public static Mapping by(String label) {

        for(Mapping m: values() {
            if(m.label.equals(label)) return m;
        }

        return null;
    }

}

所以方法by(String label)允许你通过非枚举来获取枚举值。此外,可以发明 2 个枚举之间的映射。除了“一对一”默认关系外,还可以尝试“一对多”或“多对多”

最后,enum 是一个 Java 类。因此,您可以在其中包含 main 方法,这在需要立即对 args 执行一些映射操作时可能很有用。


V
Vincent Tang

而不是制作一堆 const int 声明

您可以将它们全部分组到 1 个枚举中

所以它都是由他们所属的共同组组织的


佚名

枚举就像类。像类一样,它也有方法和属性。

与类的区别是: 1. 枚举常量是 public、static、final。 2. enum 不能用于创建对象,也不能扩展其他类。但它可以实现接口。


N
Nicolas Voron

除了@BradB 答案:

太真实了……奇怪的是,这是唯一提到这一点的答案。当初学者发现枚举时,他们很快将其视为编译器有效标识符检查的魔术。当代码打算在分布式系统上使用时,他们哭了……一个月后。保持与包含非静态值列表的枚举的向后兼容性是一个真正的问题和痛苦。这是因为当您向现有枚举添加值时,其类型会发生变化(尽管名称不会)。

“哦,等等,它看起来可能是同一种类型,对吧?毕竟,它们是同名的枚举——而且枚举不只是引擎盖下的整数吗?”由于这些原因,您的编译器可能不会在它所期望的类型本身的一个定义中标记使用另一个。但事实上,它们(在最重要的方面)是不同的类型。最重要的是,它们具有不同的数据域——给定类型可接受的值。通过添加一个值,我们有效地改变了枚举的类型,因此破坏了向后兼容性。

结论:在需要时使用它,但请检查所使用的数据域是否是有限的、已知的、固定的集合。