今天我浏览了这个网站上的一些问题,我发现提到enum
being used in singleton pattern这个解决方案所谓的线程安全性好处。
我从未使用enum
,而且我已经用Java编程了超过两年。显然他们改变了很多。现在他们甚至在自己内部全力支持OOP。
答案 0 :(得分:591)
当变量(尤其是方法参数)只能从一小组可能值中取出一个时,应始终使用枚举。例如类型常量(合同状态:“永久”,“临时”,“学徒”)或标志(“立即执行”,“延迟执行”)。
如果使用枚举而不是整数(或字符串代码),则增加编译时检查并避免传入无效常量时出错,并记录哪些值合法使用。
顺便说一句,过度使用枚举可能意味着你的方法做得太多了(通常有几个单独的方法更好,而不是一个方法需要几个标志来修改它的作用),但如果你必须使用标志或类型代码,枚举是可行的方法。作为一个例子,哪个更好?
/** 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);
不再可能。
答案 1 :(得分:126)
为什么要使用任何编程语言功能?我们有语言的原因是
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();
}
现在你可以写:
Color trafficLightColor = Color.RED;
上面的样板与
具有很多相同的效果public enum Color { RED, AMBER, GREEN };
两者都提供与编译器相同级别的检查帮助。 Boilerplate只是更多的打字。但是节省大量的输入会使程序员更加高效(见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
。 enum的良好编译器检查和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
等等(实际上有一个比较简单的模拟{{1}这将适用于切换,但它是如此棘手,它更清楚地表明为什么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
单例实际上是懒惰的,因为它在首次使用之前未初始化。 Java初始化也是同步的,因此多个线程无法初始化多个enum
实例。你得到一个懒惰的初始化单例,代码非常少。唯一不好的是相当模糊的语法。你需要知道这个习惯用法或彻底理解类加载和初始化是如何工作的,以了解发生了什么。
答案 2 :(得分:41)
除了已经提到的用例之外,我经常发现枚举对于实现策略模式很有用,遵循一些基本的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
只是一个界面。
答案 3 :(得分:32)
其他答案都没有涵盖,令枚举特别强大的是拥有template methods的能力。方法可以是基本枚举的一部分,并由每种类型覆盖。并且,通过附加到枚举的行为,它通常不需要if-else构造或切换语句作为blog post demonstrates - 其中enum.method()
执行最初将在条件内执行的内容。同样的例子还显示了使用带枚举的静态导入以及生成更清晰的DSL代码。
其他一些有趣的特质包括枚举为equals()
,toString()
和hashCode()
提供实施,并实施Serializable
和Comparable
。
对于所有枚举必须提供的完整纲要,我强烈推荐Bruce Eckel的Thinking in Java 4th edition,其中专门讨论了这一主题。特别有启发性的是涉及Rock,Paper,Scissors(即RoShamBo)游戏作为枚举的示例。
答案 4 :(得分:21)
来自Java documents -
您应该在任何时候使用枚举类型 需要代表一组固定的 常量。这包括天然枚举 我们太阳能中的行星类型 您知道的系统和数据集 编译时的所有可能值 时间 - 例如,a的选择 菜单,命令行标志等。
一个常见的例子是使用枚举类型用一组私有静态final int常量(在合理的常量数内)替换一个类。基本上,如果您认为在编译时知道“某事”的所有可能值,则可以将其表示为枚举类型。枚举为具有常量的类提供了可读性和灵活性。
我可以想到枚举类型的其他几个优点。它们总是特定枚举类的一个实例(因此使用枚举作为单例的概念到达)。另一个优点是您可以在switch-case语句中使用枚举作为类型。您还可以在枚举上使用toString()将它们打印为可读字符串。
答案 5 :(得分:18)
现在为什么以及我应该使用什么 枚举日常节目?
您可以使用Enum来表示一组固定的小常量或内部类模式,同时提高可读性。此外,当在方法参数中使用时,Enums可以强制执行一定的刚性。它们提供了将信息传递给Planets example on Oracle's site中的构造函数的有趣可能性,正如您所发现的那样,它还允许一种简单的方法来创建单例模式。
ex:Locale.setDefault(Locale.US)
读取优于Locale.setDefault(1)
,并在您添加.
分隔符而不是所有整数时强制使用IDE中显示的固定值集。
答案 6 :(得分:13)
Enum
以自我记录的方式枚举一组固定的值
它们使您的代码更加明确,而且更不容易出错。
为什么不使用String
或int
代替Enum
来获取常量?
if
)以确保您的参数在有效范围内。String
(这取决于Enum
的复杂程度。)此外,每个Enum
的实例都是一个类,您可以为其定义其个别行为。
另外,他们在创建实例时(在加载枚举时)确保线程安全,这在简化 Singleton模式方面已经有很好的应用。
This blog说明了它的一些应用程序,例如解析器的状态机。
答案 7 :(得分:12)
了解class AppMain{
def main(args : Array[String]){
val resourcePath = "/a/"
sparkConf.setExecutorEnv("RESOURCEPATH", resourcePath)
}
class B{
println(System.getenv("RESOURCEPATH")) // This returns null
}
}
与包含enums
字段和Constant
的其他类一样非常有用。
例如,
private constructor
编译器按如下方式编译它;
public enum Weekday
{
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
答案 8 :(得分:10)
enum
表示枚举,即逐一提及(很多事情)。
枚举是包含固定常量集的数据类型。
OR
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());
}
}
}
枚举的优点:
for more
答案 9 :(得分:8)
答案 10 :(得分: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
从收到的值(例如1,2)创建ProtocolType.fromInt(2)
个对象
使用myEnumObj.name
希望这有帮助。
答案 11 :(得分: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;
}
}
}
答案 12 :(得分:4)
ENum代表“枚举类型”。它是一种数据类型,具有一组固定的常量,您可以自己定义。
答案 13 :(得分:3)
枚举?为什么要使用它?我想当你使用它时会更加明白。我有同样的经历。
假设您有创建,删除,编辑和读取数据库操作。
现在,如果您将枚举创建为操作:
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可以被认为具有相同类型的设施,其中用户将以稍微不同的方式定义它将存储多少以及哪种类型的值。 :)
答案 14 :(得分:3)
在我看来,你现在得到的所有答案都是有效的,但根据我的经验,我会用几句话来表达:
如果您希望编译器检查标识符值的有效性,请使用枚举。
否则,您可以像往常一样使用字符串(可能是您为应用程序定义了一些“约定”)并且您将非常灵活......但是您不会获得100%的安全性来防止字符串上的拼写错误而且您将只在运行时实现它们。
答案 15 :(得分:2)
使用TYPE SAFETY的枚举,这是一种语言功能,所以通常会得到:
枚举可以有方法,构造函数,你甚至可以在枚举中使用枚举,并将枚举与接口结合起来。
将enums视为替换定义良好的int常量集的类型(Java&#39;继承&#39;来自C / C ++)。
本书 Effective Java 2nd Edition 有一整章关于它们,并详细介绍了它们。另请参阅this Stack Overflow post。
答案 16 :(得分:2)
Java允许您将变量限制为只有少数预定义值之一 - 换句话说,枚举列表中的一个值。
使用enums
有助于减少代码中的错误。
以下是课外enums
的示例:
enums coffeesize{BIG , HUGE , OVERWHELMING };
//This semicolon is optional.
这会将coffeesize
限制为:BIG
,HUGE
或OVERWHELMING
作为变量。
答案 17 :(得分:1)
这里有很多答案,只想指出两个具体的答案:
1)在Switch-case
语句中使用as常量。
Switch case不允许你使用String对象的情况。枚举就派上用场了。更多:http://www.javabeat.net/2009/02/how-to-use-enum-in-switch/
2)再次实施Singleton Design Pattern
- Enum,来救援。用法,在这里:What is the best approach for using an Enum as a singleton in Java?
答案 18 :(得分:1)
到目前为止,我从未需要使用枚举。我一直在阅读它们,因为它们是在1.5或版虎中引入的,因为它在当天被召回。他们从未真正为我解决过“问题”。对于那些使用它的人(我看到他们中有很多人这样做),我相信它确实可以满足一些目的。只是我的2个钱。
答案 19 :(得分:1)
什么给了我Ah-Ha时刻就是这个认识: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);
}
}
答案 20 :(得分: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;
}
}
};
答案 21 :(得分:1)
以现代方式看待旧问题
此方法通过利用Java的保证来实现单例,Java保证任何枚举值在Java程序中仅被实例化一次,并且枚举为线程安全性提供了隐式支持。由于Java枚举值是可全局访问的,因此可以用作单例。
public enum Singleton {
SINGLETON;
public void method() { }
}
这是如何工作的?好吧,代码的第二行可能被认为是这样的:
public final static Singleton SINGLETON = new Singleton();
我们得到了很好的旧的早期初始化单例。
请记住,由于这是一个枚举,因此您也始终可以通过Singleton.INSTANCE
访问实例:
Singleton s = Singleton.INSTANCE;
优点
valueOf
方法与反序列化名称一起使用以获取所需的实例。 Enum
类。无法使用反射来实例化枚举类型的对象的原因是因为Java规范不允许并且该规则在newInstance
类的Constructor
方法的实现中编码,该方法通常用于创建通过反射的物体:if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
map
对其进行管理。多例模式不会确保每个应用程序 (例如java.lang.Runtime
)(例如1/m
),而是确保单个实例每个键。每个人的详细说明都太冗长,因此我只链接了一篇好文章-All you want to know about Singleton
答案 22 :(得分:0)
根据我的经验,我看到Enum的使用有时会导致系统很难改变。如果您使用Enum来获取经常更改的一组特定于域的值,并且它有许多依赖于它的其他类和组件,您可能需要使用Enum来考虑 not 。
例如,使用Enum进行市场/交易的交易系统。那里有很多市场,几乎可以肯定会有很多子系统需要访问这个市场列表。每当您想要将新市场添加到您的系统中,或者您想要移除市场时,太阳下的所有东西都可能需要重建和发布。
更好的例子就是产品类别类型。假设您的软件管理百货商店的库存。有很多产品类别,以及为什么这个类别列表可能会发生变化的原因很多。管理人员可能希望存储新的产品线,摆脱其他产品线,并可能不时重新组织类别。如果您只是因为用户想要添加产品类别而需要重建和重新部署所有系统,那么您已经采取了一些简单快速的方法(添加类别)并使其变得非常困难和缓慢。
总而言之,如果您所表示的数据随时间变化非常静态并且依赖项数量有限,则枚举很好。但是如果数据变化很大并且有很多依赖关系,那么你需要一些在编译时没有检查过的动态(比如数据库表)。
答案 23 :(得分: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)
允许您通过非枚举获取Enumerated值。此外,可以发明2个枚举之间的映射。也可以尝试1到很多&#39;或者“多对多”&#39;除了一对一&#39;默认关系
最后,enum
是一个Java类。因此,您可以在其中包含main
方法,这可能在需要立即对args
执行某些映射操作时非常有用。
答案 24 :(得分:0)
而不是进行一堆const int声明
您可以将它们全部归为1个枚举
所以所有这些都是由它们所属的共同小组组织的
答案 25 :(得分:0)
枚举就像类。像类一样,它也具有方法和属性。
与类的区别是: 1.枚举常量是public,static,final。 2.枚举不能用于创建对象,并且不能扩展其他类。但是它可以实现接口。
答案 26 :(得分:0)
除了@BradB答案:
那是真的……很奇怪,它是唯一提到这一点的答案。当初学者发现枚举时,他们会迅速将其视为对编译器进行有效标识符检查的魔术。当打算在分布式系统上使用该代码时,他们哭了……一个月后。维护与包含非静态值列表的枚举的向后兼容性是一个令人担忧的难题。这是因为当您将值添加到现有枚举时,其类型会更改(尽管名称没有更改)。
“ Ho,等等,它看起来像是相同的类型,对吗?毕竟,它们是具有相同名称的枚举–枚举不就是幕后的整数吗?”由于这些原因,您的编译器可能不会在期望使用类型本身的另一个定义的情况下标记其使用。但是实际上,它们(以最重要的方式)是不同的类型。最重要的是,它们具有不同的数据域-特定类型可接受的值。通过添加值,我们有效地更改了枚举的类型,因此破坏了向后兼容性。
结论:可以在需要时使用它,但是,请检查使用的数据域是否是有限的,已知的固定集。