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

Mat*_* B. 464 java enums

今天我正在浏览这个网站上的一些问题,我发现提到了一个enum 单独使用模式的关于这种解决方案所谓的线程安全的好处.

我从来没有使用过enums,而且我已经用Java编程了好几年了.显然他们改变了很多.现在他们甚至在自己内部全力支持OOP.

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

sle*_*ske 606

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

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

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

举个例子,哪个更好?

/** 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)
Run Code Online (Sandbox Code Playgroud)

/** 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)
Run Code Online (Sandbox Code Playgroud)

方法调用如:

int sweetFoobangCount = countFoobangs(3);
Run Code Online (Sandbox Code Playgroud)

然后变成:

int sweetFoobangCount = countFoobangs(FB_TYPE.SWEET);
Run Code Online (Sandbox Code Playgroud)

在第二个示例中,立即清楚允许哪些类型,文档和实现不会不同步,并且编译器可以强制执行此操作.另外,无效的通话就像

int sweetFoobangCount = countFoobangs(99);
Run Code Online (Sandbox Code Playgroud)

不再可能.

  • 如果您使用枚举并希望允许组合值,请使用EnumSet.这包括所有类型的整洁助手,例如public static final EnumSet <FB_TYPE> ALL = EnumSet.allOf(FB_TYPE.class); (40认同)
  • 这些答案是我真的很想看到的那些答案,因为有人已经付出了一些实际的努力,干得好,我现在理解了这些答案! (17认同)
  • @Elist 使用枚举增加了代码的可读性,如果您在某些时候使用常量,您或您的继任者不知道为什么使用它...... :) (2认同)

Gen*_*ene 131

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

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

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

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();
}
Run Code Online (Sandbox Code Playgroud)

现在你可以写:

Color trafficLightColor = Color.RED;
Run Code Online (Sandbox Code Playgroud)

上面的样板与效果大致相同

public enum Color { RED, AMBER, GREEN };
Run Code Online (Sandbox Code Playgroud)

两者都提供了与编译器相同级别的检查帮助.Boilerplate只是更多的打字.但是节省大量的输入会使程序员更有效率(见1),所以这是一个有价值的功能.

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

切换语句

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

Color color = ... ;
switch (color) {
    case RED:
        ...
        break;
}
Run Code Online (Sandbox Code Playgroud)

注意它不是Color.RED在这种情况下.如果您不使用枚举,则使用命名数量的唯一方法switch是:

public Class Color {
    public static final int RED = 0;
    public static final int AMBER = 1;
    public static final int GREEN = 2;
}
Run Code Online (Sandbox Code Playgroud)

但现在保持颜色的变量必须具有类型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);
}
Run Code Online (Sandbox Code Playgroud)

现在:

Color color = ... ;
switch (color.tag) {
    case Color.RED_TAG:
        ...
        break;
}
Run Code Online (Sandbox Code Playgroud)

但请注意,更多的样板!

使用枚举作为单身人士

从上面的样板文件中,您可以看到为什么枚举提供了实现单例的方法.而不是写:

public class SingletonClass {
    public static final void INSTANCE = new SingletonClass();
    private SingletonClass() {}

    // all the methods and instance data for the class here
}
Run Code Online (Sandbox Code Playgroud)

然后使用

SingletonClass.INSTANCE
Run Code Online (Sandbox Code Playgroud)

我们可以说

public enum SingletonClass {
    INSTANCE;

    // all the methods and instance data for the class here
}
Run Code Online (Sandbox Code Playgroud)

这给了我们同样的东西.我们可以侥幸成功,因为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
}
Run Code Online (Sandbox Code Playgroud)

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

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

enum,因为它未初始化,直到第一次使用单是有效的懒人.Java初始化也是同步的,因此多个线程无法初始化多个实例INSTANCE.你得到一个懒惰的初始化单例,代码非常少.唯一不好的是相当模糊的语法.你需要知道这个习惯用法或彻底理解类加载和初始化是如何工作的,以了解发生了什么.

  • 最后一段确实澄清了我的单身情况.谢谢!任何阅读的读者都应该重新阅读. (15认同)

Cos*_*atu 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());
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

ora*_*ips 32

其他答案都没有涵盖,使枚举特别强大的是能够使用模板方法.方法可以是基本枚举的一部分,并由每种类型覆盖.并且,通过附加到枚举的行为,它通常消除了对if-else构造或switch语句的需要,正如此博客文章所示 - enum.method()最初将在条件内执行的内容.同样的例子还显示了使用带枚举的静态导入以及生成更清晰的DSL代码.

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

有关枚举所提供的所有内容的完整概述,我强烈推荐Bruce Eckel的Thinking in Java第4版,其中专门讨论了这一主题.特别有启发性的是涉及Rock,Paper,Scissors(即RoShamBo)游戏作为枚举的示例.


Coo*_*ans 22

来自Java 文档 -

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

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

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

  • 你会在一年中的所有366天使用枚举吗?它是常数的固定集(366)(日期不变).所以这表明你应该.: - /我会限制固定集的大小. (8认同)
  • @marcog - LOL - 好点.我添加了免责声明! (3认同)
  • @marcog通过一些聪明的思考,可以用紧凑的枚举来概括每个月的一周和几天. (2认同)
  • 说到星期几…… [`DayOfWeek`](http://docs.oracle.com/javase/8/docs/api/java/time/DayOfWeek.html) 枚举现在是预定义的,内置于​​ Java 8以及后来作为 [java.time](http://docs.oracle.com/javase/8/docs/api/java/time/package-summary.html) 框架的一部分(和 [反向移植到 Java 6 &amp; 7](http://www.threeten.org/threetenbp/) 和 [到 Android](https://github.com/JakeWharton/ThreeTenABP))。 (2认同)

Jam*_* P. 19

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

您可以使用Enum来表示一组固定的小常量或内部类模式,同时提高可读性.此外,当在方法参数中使用时,Enums可以强制执行一定的刚性.它们提供了将信息传递给构造函数的有趣可能性,就像Oracle网站上Planets示例一样,并且正如您所发现的那样,它还允许一种简单的方法来创建单例模式.

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


afs*_*tos 13

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

为什么不使用String,或int代替Enum常数?

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

而且,每个Enum实例都是一个类,您可以为其定义其个体行为.

此外,它们在创建实例时(在加载枚举时)确保线程安全,这在简化Singleton模式中有很大的应用.

此博客说明了它的一些应用程序,例如解析器的状态机.


Ad *_*tum 13

知道它enums就像其他带有Constant字段和类的类一样很有用private constructor.

例如,

public enum Weekday
{
  MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
} 
Run Code Online (Sandbox Code Playgroud)

编译器将其编译如下;

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...
}
Run Code Online (Sandbox Code Playgroud)


Pre*_*raj 12

enum装置枚举关合作即提(一些事情)一个接一个.

一个枚举是包含组固定常数的数据类型.

要么

An enum就像a 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());
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

枚举的优点:

  • 枚举在编译时检查时提高了类型安全性,以避免在运行时出错.
  • 枚举可以很容易地用在开关中
  • 枚举可以遍历
  • 枚举可以有字段,构造函数和方法
  • 枚举可以实现许多接口但不能扩展任何类,因为它在内部扩展了Enum类

对于更多


tin*_*iry 9

什么是枚举

  • 枚举是为枚举定义的新数据类型的关键字.应该大量使用Typesafe枚举.特别是,它们是许多旧API中用于表示相关项集的简单String或int常量的强大替代方法.

为什么要使用枚举

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

注意

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

  • 您没有解释为什么要使用枚举.您只需在不相关的标题下列出枚举的几个属性. (4认同)

sEr*_*vIL 8

除了其他人所说的..在我过去工作的旧项目中,实体(独立应用程序)之间的大量通信使用了代表一小组的整数.将set声明为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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

希望这可以帮助.


abi*_*rai 6

Enum继承了Object类和抽象类的所有方法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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Chr*_*ssy 5

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

  • 编译器支持(立即查看类型问题)
  • IDE 中的工具支持(切换案例中的自动完成、缺失案例、强制默认等)
  • 在某些情况下,枚举性能也很棒(EnumSet,传统基于 int 的“位标志”的类型安全替代品

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

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

Effective Java 2nd Edition》一书有一整章关于它们,并有更详细的介绍。另请参阅此堆栈溢出帖子


归档时间:

查看次数:

278032 次

最近记录:

5 年,11 月 前