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


488

今天,我浏览了该站点上的一些问题,发现提到了enum 以单例模式使用这种方法,据称该解决方案具有线程安全优势。

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

现在为什么要在日常编程中使用枚举?为什么?


6
在他的书有效的Java,第二版,约书亚布洛赫阐述了这种方法的第3项:与私有构造函数或枚举类型强制singleton属性,在再版博士道博的
2011年


您无法实例化枚举,因为它具有私有构造函数,它们是在jvm启动时实例化的,因此其单例,枚举不是线程安全的。
Arnav Joshi

Answers:


633

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

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

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

例如,哪个更好?

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

不再可能。


41
如果使用枚举,并且希望允许值的组合,请使用EnumSet。它带有各种简洁的助手,例如公共静态最终EnumSet <FB_TYPE> ALL = EnumSet.allOf(FB_TYPE.class);。
RobAu

20
这些答案是我真的很喜欢在SO上看到的答案,因为有人付出了一些实际的努力,现在我明白了列举的好工作!
2013年

1
我认为那不是真的you should *always* use enums when a variable can only take one out of a small set of possible values。将常量值用作“二进制标志”(带有逻辑位or)对于内存不足的实时应用程序可能很有用。
Elist 2014年

@Elist使用枚举可提高代码的可读性,如果您在某个时间使用常量,则您或您的后继者不知道为什么使用常量...:)
theRoot 2015年

136

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

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

但是现在保存颜色的变量必须具有type 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,将与开关的工作,但它是如此棘手,它甚至更清楚地说明了为什么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
}

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

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

enum,因为它未初始化,直到第一次使用单是有效的懒人。Java初始化也已同步,因此多个线程不能初始化的多个实例INSTANCE。您将获得很少的代码来懒惰地初始化单例。唯一的缺点是语法晦涩难懂。您需要了解惯用语或彻底了解类加载和初始化的工作方式以了解发生的情况。


15
最后一段确实为我澄清了单例情况。谢谢!略读的任何读者都应该重新阅读。
罗勒·布尔克

我正在阅读stackoverflow.com/questions/16771373/…。现在,我对您的最后一段感到困惑。枚举如何不提供外行初始化功能?
Deepankar Singh

static final字段是在类初始化时(而不是加载时)初始化的。它与enum常量初始化完全相同(实际上,它们在幕后完全相同)。这就是为什么即使在第一个Java版本中,尝试为单例实现“聪明的”惰性初始化代码也总是没有意义的原因。
Holger

42

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

  1. 将代码放在数据所在的位置(即在枚举本身内,或者通常在枚举常量内,这可能会覆盖方法)。
  2. 实现一个(或多个)接口以便不将客户端代码绑定到枚举(枚举仅应提供一组默认实现)。

最简单的示例是一组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服务层所看到的仅仅是一个接口。


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

32

使枚举特别强大的其他答案之一是具有模板方法的能力。方法可以是基本枚举的一部分,并且可以按每种类型覆盖。并且,由于此行为附加到枚举,因此通常消除了对此博客文章所演示的 if-else构造或switch语句的需要-在enum.method()条件内最初将执行什么操作。相同的示例还显示了将静态导入与枚举一起使用,以及生成更清晰的DSL之类的代码。

其他一些有趣的特质包括事实枚举提供实施equals()toString()hashCode()贯彻SerializableComparable

要完整地列举所有枚举,我强烈推荐Bruce Eckel的《Thinking in Java》第4版,其中整章专门介绍了该主题。带有枚举的涉及剪刀,石头,剪刀(即RoShamBo)游戏的示例尤其具有启发性。


23

从Java 文档 -

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

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

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


8
一年中所有366天都使用枚举吗?它是固定的一组常量(366)(日期不变)。因此,这建议您应该这样做。:-/我会限制固定集的大小。
moinudin

2
@marcog稍微思考一下,就有可能在一个紧凑的枚举中总结一周中的某几天以及每个月中的几天。
James P.

1
说到星期几… DayOfWeek枚举现已预定义,已内置到Java 8中,后来又作为java.time框架的一部分内置(并反向移植到Java 6和7以及Android)。
罗勒·布尔克

19

现在为什么要在日常编程中使用枚举?为什么?

您可以使用Enum表示较小的固定常量集或内部类模式,同时提高可读性。同样,在方法参数中使用枚举时,枚举可以具有一定的刚性。它们提供了将信息传递给构造函数的有趣可能性,就像Oracle网站上Planets示例一样,并且您已经发现,还提供了一种简单的方法来创建单例模式。

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


@arnt我也已修复问题中的语法。谢谢。
James P.

13

Enums以自我记录的方式枚举一组固定的值。
它们使您的代码更加明确,也减少了出错的可能性。

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

  1. 编译器不允许输入错误,也不允许固定集之外的任何值,因为枚举本身就是类型。后果:
    • 您不必编写前提条件(或手册if)来确保您的参数在有效范围内。
    • 类型不变免费的午餐。
  2. 枚举可以像其他任何类一样具有行为。
  3. String无论如何,您可能都需要使用类似数量的内存(这取决于的复杂度Enum)。

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

另外,它们在实例创建时(加载枚举时)确保线程安全,这在简化Singleton Pattern中已得到了广泛的应用。

该博客说明了其某些应用程序,例如解析器的状态机


13

知道这enums和其他带有Constantfield和a的类一样很有用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...
}

12

enum表示枚举,即一一提及。

一个枚举是包含固定设置常数的数据类型。

要么

An enum就像一个class,在编译时已知一组固定的实例。

例如:

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类

对于更多


9

什么是枚举

  • 枚举是为新数据类型的枚举定义的关键字。类型安全的枚举应自由使用。特别是,它们是较旧的API中用来表示相关项集的简单String或int常量的可靠替代。

为什么要使用枚举

  • 枚举是java.lang.Enum的隐式最终子类
  • 如果枚举是类的成员,则它是隐式静态的
  • new永远不能与枚举一起使用,即使在枚举类型本身内也是如此
  • name和valueOf仅使用枚举常量的文本,而如果需要,可以覆盖toString以提供任何内容
  • 对于枚举常量,等于和==等于同一事物,可以互换使用
  • 枚举常量是隐式的public static final

注意

  • 枚举不能扩展任何类。
  • 枚举不能是超类。
  • 枚举常量的出现顺序称为其“自然顺序”,并且还定义了其他项使用的顺序:compareTo,值的迭代顺序,EnumSet,EnumSet.range。
  • 枚举可以具有构造函数,静态块和实例块,变量和方法,但不能具有抽象方法。

4
您没有解释为什么使用枚举。您只需在不相关的标题下列出枚举的几个属性。
Duncan Jones

@DuncanJones的final,static术语没有给出使用枚举的目的吗?
tinker_fairy 2013年

8

除了别人说的话。在我以前工作的一个较旧的项目中,实体(独立应用程序)之间的大量通信使用的是整数,代表整数。这是宣布一套有用enum的静态方法来获取enum从对象value,反之亦然。该代码看起来更简洁,切换了案例可用性,并且更容易写入日志。

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

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

希望这可以帮助。


6

枚举继承了Objectclass和abstract class的所有方法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;
        }
    }
}

4

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


您尚未说明为什么使用枚举。
Duncan Jones

4
是的,我做到了。您可以使用它来存储自己定义的一组固定常数。它的具体应用由您决定。无需说何时,为什么或如何使用某些东西。这完全符合开发人员的需求。您需要了解的只是它的含义及其工作方式。
史蒂夫,

4

我认为,您到目前为止所获得的所有答案都是有效的,但以我的经验,我会用几句话来表达:

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

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


3

对类型安全使用枚举,这是一种语言功能,因此您通常会得到:

  • 编译器支持(立即查看类型问题)
  • IDE中的工具支持(在切换情况下自动完成,缺少情况下自动强制执行...)
  • 在某些情况下,枚举性能也很好(EnumSet,这是传统的基于int的“位标志”的类型安全替代品)。

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

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

有效的Java 2nd Edition》一书中有关于它们的整章内容,并涉及更多细节。另请参阅此Stack Overflow帖子


2

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

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

这限制coffeesize到具有任一:BIGHUGE,或OVERWHELMING为一个变量。


2

枚举?为什么要使用它?我认为您将在何时使用它。我有相同的经验。

假设您具有创建,删除,编辑和读取数据库的操作。

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

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视为具有相同类型的设施,用户将以稍微不同的方式定义将存储多少和哪种类型的价值。:)


1

到目前为止,我从来不需要使用枚举。我一直在阅读有关它们的信息,因为它们是在1.5或Tiger版本中引入的,这在当时被人们称为。他们从来没有真正解决过我的“问题”。对于那些使用它的人(我看到很多人都这样做),请确保它一定有一定用途。只是我的两点。



1

让我感到惊奇的是这一认识: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);
    }
}

1

对于我来说,使代码在将来更具可读性,下一个代码段代表了最有用的枚举案例:

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

1

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

例如,一个使用Enum进行市场/交易的交易系统。那里有很多市场,几乎可以肯定,会有很多子系统需要访问此市场列表。每当您希望将新市场添加到系统中时,或者如果您要删除市场,则可能必须重新构建和释放所有在阳光下的东西。

一个更好的例子是产品类别类型。假设您的软件管理百货商店的库存。有很多产品类别,并且有很多原因可以更改此类别列表。经理可能想要库存新的产品线,摆脱其他产品线,并可能不时重组类别。如果仅由于用户想要添加产品类别而不得不重建和重新部署所有系统,那么您所采取的措施应该既简单又快速(添加类别),并且变得非常困难和缓慢。

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


1

基于枚举的单例

现代看待一个老问题

该方法通过利用Java的保证(在Java程序中任何枚举值仅实例化一次)和枚举为线程安全性提供隐式支持,来实现单例。由于Java枚举值可全局访问,因此可以用作单例。

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

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

public final static Singleton SINGLETON = new Singleton(); 

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

请记住,由于这是一个枚举,因此您也始终可以通过以下方式访问实例Singleton.INSTANCE

Singleton s = Singleton.INSTANCE;
优点
  • 为了防止在反序列化期间创建单例的另一个实例,请使用基于枚举的单例,因为JVM会注意枚举的序列化。枚举序列化和反序列化的工作方式与普通Java对象不同。唯一要序列化的是枚举值的名称。在反序列化过程中,枚举valueOf方法与反序列化名称一起使用以获取所需的实例。
  • 基于枚举的单例可以保护自己免受反射攻击。枚举类型实际上扩展了java Enum类。不能使用反射来实例化枚举类型的对象的原因是因为java规范不允许,并且该规则newInstanceConstructor类的方法的实现中编码,该方法通常用于通过反射创建对象:
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
    throw new IllegalArgumentException("Cannot reflectively create enum objects");
  • 不应克隆枚举,因为每个值必须恰好有一个实例。
  • 所有单例实现中最简洁的代码。
缺点
  • 基于枚举的单例不允许延迟初始化。
  • 如果您更改了设计并希望将单身人士转换为多身人士,则枚举将不允许这样做。multiton模式用于受控创建多个实例,可通过使用进行管理map。而不是具有单个实例每个应用程序(例如,java.lang.Runtime)的多例图案代替确保单个实例每个键
  • 枚举仅出现在Java 5中,因此您不能在以前的版本中使用它。

单例模式有几种实现,每种都有优点和缺点。

  • 渴望加载单身人士
  • 双重检查锁定单身人士
  • 按需初始化持有人惯用语
  • 基于枚举的单例

详细说明每个都太冗长,因此我只链接了一篇好文章- 您想了解的有关Singleton的全部信息


0

我会使用枚举作为有用的映射工具,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)允许您通过非枚举获取枚举值。此外,可以发明两个枚举之间的映射。除了“一对一”默认关系外,还可以尝试“一对多”或“多对多”

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


0

而不是进行一堆const int声明

您可以将它们全部归为1个枚举

所以所有这些都是由他们所属的共同组织


0

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

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


0

除了@BradB回答:

的确如此……很奇怪,这是唯一提到这一点的答案。当初学者发现枚举时,他们会迅速将其视为对编译器进行有效标识符检查的魔术。当打算在分布式系统上使用该代码时,他们哭了……一个月后。保持与包含非静态值列表的枚举的向后兼容性是一个令人担忧的难题。这是因为当您将值添加到现有枚举时,其类型会更改(尽管名称没有更改)。

“ Ho,等等,它看起来像是相同的类型,对吧?毕竟,它们是具有相同名称的枚举–枚举不就是幕后的整数吗?” 出于这些原因,您的编译器可能不会在期望其他类型的地方标记该类型本身的一种定义。但实际上,它们(以最重要的方式)是不同的类型。最重要的是,它们具有不同的数据域-值对于给定的类型是可以接受的。通过添加值,我们有效地更改了枚举的类型,因此破坏了向后兼容性。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.