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

今天,我浏览了该站点上的一些问题,发现提到了一个单例模式中使用enum ,该enum声称该解决方案具有线程安全性。

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

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


#1楼

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


#2楼

什么是枚举

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

为什么要使用枚举

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

注意

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

#3楼

除了别人所说的以外。在我以前工作的一个较旧的项目中,实体(独立应用程序)之间的大量通信使用的是整数,代表整数。 使用静态方法将该集合声明为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写入日志

希望这可以帮助。


#4楼

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

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

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


#5楼

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

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

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

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

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

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


#6楼

除了已经提到的用例之外,我还经常遵循一些基本的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只是一个接口。


#7楼

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


#8楼

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

  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 ,该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
}

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

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

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


#9楼

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

#10楼

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

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

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

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


#11楼

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

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

2)实施Singleton Design Pattern -再次枚举,请急救。 用法,这里: 在Java中将枚举用作单例的最佳方法是什么?


#12楼

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

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

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

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

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


#13楼

enum是指枚举,即一一提及。

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

要么

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类

更多


#14楼

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

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

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


#15楼

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

#16楼

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

#17楼

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

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

#18楼

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

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

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

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


#19楼

从Java 文档 -

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

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

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


#20楼

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

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

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

例如,哪个更好?

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

不再可能。


#21楼

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

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

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


#22楼

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

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

要完整地列举所有枚举,我强烈建议Bruce Eckel撰写的《 Java思维》第4版 ,其中整整一章专门讨论该主题。 带有枚举的涉及剪刀,石头,剪刀(即RoShamBo)游戏的示例尤其具有启发性。


#23楼

我将枚举用作有用的映射工具, if-else实现了某些方法,则避免使用多次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进行一些映射操作时可能很有用。


#24楼

而不是进行一堆const int声明

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

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

发布了0 篇原创文章 · 获赞 2 · 访问量 5912

猜你喜欢

转载自blog.csdn.net/asdfgh0077/article/details/104070317