什么是枚举?为什么它们有用?
今天我浏览了这个网站上的一些问题,并且发现了一个单引擎模式中使用的enum
,提到了这种解决方案所谓的线程安全性好处。
我从来没有使用过enum
而且我已经用Java编写了超过一年的夫妇了。 显然他们改变了很多。 现在他们甚至在自己的内部完全支持OOP。
现在为什么我应该在日常编程中使用枚举?
当变量(尤其是方法参数)只能从一小组可能的值中取出一个时,您应该始终使用枚举。 例如类型常量(合同状态:“永久”,“临时”,“学徒”)或标志(“立即执行”,“延迟执行”)。
如果使用枚举而不是整数(或字符串代码),则可以增加编译时检查并避免传入无效常量的错误,并记录哪些值可合法使用。
顺便说一句,过度使用枚举可能意味着你的方法做得太多(通常有更好的方法,而不是一个方法需要几个标志来修改它所做的),但是如果你必须使用标志或类型代码,枚举是要走的路。
举个例子,哪个更好?
/** 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);
不再是可能的。
为什么使用任何编程语言功能? 我们有语言的原因是
- 程序员能够有效并正确地表达算法在计算机中可以使用的形式。
- 维护人员理解他人编写的算法并正确进行修改。
枚举提高了正确性和可读性的可能性,而无需编写大量的样板文件。 如果你愿意写样板,那么你可以“模拟”枚举:
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枚举是作为完整的类来实现的,只有少量的语法糖洒在顶部。 这又是一个较少的样板,但除非这个成语对你来说是熟悉的,否则它是不明显的。 我也不喜欢这样一个事实:即使对于单例: ord
和values
等没有多少意义,你也可以获得各种枚举函数(实际上, 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
仍然为空,则可以创建任意数量的实例。 这不好。 唯一的解决办法是添加synchronized
访问来保护变量INSTANCE
。
但是,上面的static final
代码没有这个问题。 它在课堂加载时刻急切地创建实例。 类加载是同步的。
enum
单例是有效的懒惰,因为它没有初始化直到第一次使用。 Java初始化也是同步的,因此多个线程不能初始化多个INSTANCE
。 你得到一个懒惰的初始化的单身代码很少。 唯一的缺点是相当模糊的语法。 你需要知道这个习惯用法,或者彻底了解类加载和初始化是如何工作的,以便知道发生了什么。
除了已经提到的用例之外,我还经常在遵循一些基本的面向对象的指导原则的基础上,发现枚举对实现策略模式有用:
- 将代码放在数据所在的位置(也就是说,在枚举本身内 – 或者常常在枚举常量内,这可能会覆盖方法)。
- 实现一个接口(或更多),以便不将客户端代码绑定到枚举(应只提供一组默认实现)。
最简单的例子是一组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(s1, s2); } }, LENGTH { @Override public int compare(String s1, String s2) { return new Integer(s1.length()).compareTo(s2.length()); } }; }
这个“模式”可以在更复杂的场景中使用,大量使用enum附带的所有东西:遍历实例,依赖于它们的隐式顺序,通过名称检索实例,提供正确实例的静态方法对于特定的上下文等。而且你仍然有这个隐藏在界面后面,所以你的代码将与自定义实现工作,而不需要修改,以防止你想要的东西在“默认选项”中不可用。
我已经看到这个成功应用于时间粒度(每日,每周等)的概念的建模,其中所有的逻辑封装在枚举中(为给定的时间范围选择正确的粒度,将每个粒度绑定的特定行为定义为常量方法等)。 而服务层看到的Granularity
只是一个界面。
其他答案没有任何其他答案已经涵盖使枚举特别强大的是有能力有模板方法 。 方法可以是基本枚举的一部分,并且可以被每个类型覆盖。 而且,通过附加到枚举上的行为,它通常会消除if-else结构或switch语句的需要,正如本博文所示 – 其中enum.method()
会执行最初将在条件中执行的内容。 同样的例子也显示了使用枚举静态导入以及生成更清洁的DSL代码。
其他一些有趣的特性包括枚举为equals()
, toString()
和hashCode()
提供实现并实现Serializable
和Comparable
。
对于所有枚举必须提供的完整概要,我强烈推荐Bruce Eckel的Thinking in Java第4版 ,这个章节专门讨论这个话题。 特别照亮的是涉及作为枚举的Rock,Paper,Scissors(即RoShamBo)游戏的例子。
从Java 文件 –
任何时候你需要使用枚举类型来表示一组固定的常量。 这包括自然枚举类型,例如太阳系中的行星,以及在编译时知道所有可能值的数据集,例如菜单上的选项,命令行标志等等。
一个常见的例子是用一组私有静态final int常量(在合理数目的常量内)用一个枚举类型替换一个类。 基本上如果你认为在编译时你知道所有可能的“东西”的值,你可以把它表示为一个枚举类型。 枚举提供了一个常量类的可读性和灵活性。
我能想到的枚举类型还有其他一些优点。 它们总是一个特定枚举类的一个实例(因此使用枚举作为单例的概念到达)。 另一个好处是你可以使用枚举作为switch-case语句中的一个类型。 你也可以在枚举上使用toString()把它们打印成可读的字符串。
现在为什么我应该在日常编程中使用枚举?
您可以使用枚举来表示一个固定的一组常量或一个内部类模式,同时提高可读性。 此外,枚举可以在方法参数中使用时强制一定的刚性。 他们提供了将信息传递给构建器的有趣的可能性,就像在Oracle网站上的Planets例子中那样,并且如你所发现的那样,也允许一种简单的方法来创建单例模式。
例如: Locale.setDefault(Locale.US)
读取比Locale.setDefault(1)
更好,并且在添加时强制使用IDE中显示的一组固定值.
分隔符而不是所有整数。
Enum
以自我记录的方式列举了一组固定的值。
他们使你的代码更加明确,也更不容易出错。
为什么不使用String
,或int
,而不是常量的Enum
?
- 编译器不会允许拼写错误 ,既不是固定的值,因为枚举类型是它们自己的。 后果:
- 你不需要写一个前置条件 (或者手册
if
)来保证你的参数在有效范围内。 - 类型不变式是免费的。
- 你不需要写一个前置条件 (或者手册
- 枚举可以像任何其他类一样具有行为。
- 你可能需要类似数量的内存来使用
String
,无论如何(这取决于Enum
的复杂性)。
而且, Enum
的每一个实例都是一个类,你可以定义它的个人行为。
另外,它们在创建实例时确保线程安全 (枚举被加载时),这在简化单例模式方面已经有了很大的应用。
这个博客展示了它的一些应用程序,例如一个解析器的状态机 。
除了别人所说的话之外,在我以前为之工作的一个较旧的项目中,实体(独立应用程序)之间的大量通信使用了代表一小组的整数。 把这个set声明为enum
使用静态方法来从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
对象ProtocolType.fromInt(2)
使用myEnumObj.name
写入日志
希望这可以帮助。
What is a enum:
- 枚举是为枚举新数据类型定义的关键字。 类型安全枚举应该被宽松地使用。特别是,它们是用于表示许多相关项目集合的简单字符串或int常量的强大替代方法。
Why to use enums:
- 枚举是java.lang.Enum的隐式最终子类
- 如果一个枚举是一个类的成员,它是隐式静态的
- 新的永远不能用于枚举,即使在枚举类型本身
- 名称和值只是使用枚举常量的文本,而toString可能会被覆盖,以提供任何内容,如果需要
- 对于枚举常量,equals和==等于相同的事物,并且可以互换使用
- 枚举常量隐式公共静态最后
Note:
- 枚举不能扩展任何类。
- 一个枚举不能成为超类。
- enum常量的出现顺序称为它们的“自然顺序”,并定义其他项所使用的顺序:compareTo,值的迭代顺序,EnumSet,EnumSet.range。
- 枚举可以有构造函数,静态和实例块,变量和方法,但不能有抽象方法。
除了这里的好的答案之外,知道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... }
枚举继承了Object
类和抽象类Enum
所有方法。 所以你可以使用它的方法进行反射,多线程,serilization,比较等。如果你只是声明一个静态常量,而不是枚举,你不能。 除此之外,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; } } }
枚举代表“枚举类型”。 这是一个数据类型,有一个固定的常量集,你自己定义。
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类
为更多
在我看来,所有的答案都是有效的,但根据我的经验,我会用几句话来表达:
如果您希望编译器检查标识符值的有效性,请使用枚举。
否则,你可以像以前一样使用字符串(可能你为你的应用程序定义了一些“约定”),而且你将会非常灵活…但是你不会100%的抵制字符串上的拼写错误,你只会意识到它们在运行时。
枚举? 为什么要使用。 我认为它更多的时候会用到它。 我有同样的经历。
假设您已经创建,删除,编辑和读取数据库操作
现在如果你创建一个枚举作为操作
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
所以你可以用很多方式来使用它。 因为上例中的Db操作可以通过检查currentOperation来控制,所以对于具体的东西来说枚举总是很好的。 也许可以这么说,这可以通过变量和整数值来实现。但是我相信Enum是一个更安全的程序员。
另一件事:我认为每个程序员都喜欢布尔值不对,因为它只能存储两个值,两个具体的值。 所以Enum可以被认为具有相同类型的设施,用户将以稍微不同的方式来定义它将存储多少和什么类型的值。 🙂
Java允许您将变量限制为只有一个预定义值 – 换言之,枚举列表中的一个值。 使用enums
可以帮助减少代码中的错误。 下面是一个类之外的enums
的例子:
enums coffeesize{BIG , HUGE , OVERWHELMING }; //This semicolon is optional.
这将coffeesize
限制为具有: BIG
, HUGE
或OVERWHELMING
作为变量。
到目前为止,我从来不需要使用枚举。 自从1.5版或老虎版被引入后,我就一直在阅读它们,因为它在当天被叫回来。 他们从来没有真正为我解决过一个“问题”。 对于那些使用它的人来说(我看到他们中的很多人都这么做),我确信这肯定有用。 只是我2的钱。
这里有很多答案,只是想指出两个具体的答案:
1)在Switch-case
语句中使用常量。 切换大小写不允许你使用String对象。 枚举派上用场。 更多: http : //www.javabeat.net/2009/02/how-to-use-enum-in-switch/
2)实现Singleton Design Pattern
– Enum再次来救援。 用法,在这里: 在Java中使用Enum作为单例的最佳方法是什么?
使用枚举TYPE SAFETY,这是一个语言功能,所以你通常会得到:
- 编译器支持(立即看类型问题)
- IDE中的工具支持(开关情况下的自动完成…)
枚举可以有方法,构造函数,你甚至可以在枚举里面使用枚举并且把枚举和接口结合起来。
把枚举想象成类型来取代定义好的一组int常量(Java从C / C ++继承而来)。
“有效的Java第二版”一书全面介绍了它们并详细介绍了它。 也看到这个stackoverflow 后 。
是什么给了我啊哈的时刻是这样的认识:Enum有一个私人的构造函数,只能通过public枚举来访问:
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); } }
至于我将代码在将来可读的情况下,最有用的枚举情况在下面的代码片段中表示:
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; } } };
枚举是java中多个静态final变量的传统定义的替代,当你有一个合理的固定常数集时,它被高度使用。
我看到枚举的最重要的好处是当你定义一个方法接受一个常量参数,这样你强制方法调用者提交到预定义的常量,并防止他们传递随机常量值。
参考: 如何在java中使用枚举
根据我的经验,我看到Enum的使用有时会导致系统很难改变。 如果您使用Enum来获取一组频繁更改的特定于域的值,并且有许多依赖于其的类和组件,则可能需要考虑不使用Enum。
例如,使用Enum进行市场/交易的交易系统。 那里有很多市场,几乎可以肯定的是会有很多子系统需要访问这个市场。 每当你想把一个新的市场添加到你的系统中,或者如果你想移除一个市场,那么在阳光下的一切都有可能被重建和释放。
一个更好的例子就像产品类别类型。 假设您的软件管理百货商店的库存。 有很多产品类别,以及这个类别列表可能会改变的很多原因。 管理人员可能想要存储新的产品线,摆脱其他产品线,并可能不时地重新组织类别。 如果因为用户想要添加产品类别而必须重新构建和重新部署所有系统,那么您已经采取了一些应该简单快捷的方法(添加一个类别),使其变得非常困难和缓慢。
底线,如果您所代表的数据随着时间的推移非常静态并且依赖性数量有限,那么枚举是很好的。 但是如果数据变化很大,并且有很多的依赖关系,那么你需要一些在编译时没有被检查的动态的东西(比如数据库表)。