今天,我浏览了该站点上的一些问题,发现提到了enum
以单例模式使用这种方法,据称该解决方案具有线程安全优势。
我从未使用过enum
s,并且我使用Java编程已经有两年多了。显然,他们改变了很多。现在,他们甚至在自己内部提供了对OOP的全面支持。
今天,我浏览了该站点上的一些问题,发现提到了enum
以单例模式使用这种方法,据称该解决方案具有线程安全优势。
我从未使用过enum
s,并且我使用Java编程已经有两年多了。显然,他们改变了很多。现在,他们甚至在自己内部提供了对OOP的全面支持。
Answers:
当变量(尤其是方法参数)只能从一小部分可能的值中取出一个时,应始终使用枚举。例如类型常量(合同状态:“永久”,“临时”,“学徒”)或标志(“立即执行”,“推迟执行”)。
如果使用枚举而不是整数(或字符串代码),则将增加编译时检查并避免错误传递无效常量,并记录哪些值是合法使用的。
顺便说一句,枚举的过度使用可能意味着您的方法做得太多(通常最好有几个单独的方法,而不是一个带有几个修改其功能的标志的方法),但是如果您必须使用标志或键入代码,则枚举是要走的路。
例如,哪个更好?
/** 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);
不再可能。
you should *always* use enums when a variable can only take one out of a small set of possible values
。将常量值用作“二进制标志”(带有逻辑位or
)对于内存不足的实时应用程序可能很有用。
为什么要使用任何编程语言功能?我们完全有语言的原因是为了
枚举可提高正确性和可读性的可能性,而无需编写很多样板。如果您愿意编写样板,则可以“模拟”枚举:
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枚举被实现为完整的类,并且只在顶部撒了一点语法糖。再次减少了样板,但是除非您熟悉该成语,否则它不是显而易见的。我也喜欢你得到的各种枚举功能,即使他们没有为单身多大意义的事实: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
仍为null 时同时调用,则可以创建任意数量的实例。这是不好的。唯一的解决方案是添加synchronized
访问权限以保护变量INSTANCE
。
但是,static final
上面的代码不存在此问题。它在类加载时急切地创建实例。类加载已同步。
的enum
,因为它未初始化,直到第一次使用单是有效的懒人。Java初始化也已同步,因此多个线程不能初始化的多个实例INSTANCE
。您将获得很少的代码来懒惰地初始化单例。唯一的缺点是语法晦涩难懂。您需要了解惯用语或彻底了解类加载和初始化的工作方式以了解发生的情况。
static final
字段是在类初始化时(而不是加载时)初始化的。它与enum
常量初始化完全相同(实际上,它们在幕后完全相同)。这就是为什么即使在第一个Java版本中,尝试为单例实现“聪明的”惰性初始化代码也总是没有意义的原因。
除了已经提到的用例之外,我还经常遵循一些基本的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
服务层所看到的仅仅是一个接口。
CASE_INSENSITIVE { @Override public int compare(String s1, String s2) { return s1.compareToIgnoreCase(s2); }
。尚未提及的一个优势是您获得了强大的序列化支持。持久形式仅包含类名和常量名,而不依赖于比较器的任何实现细节。
使枚举特别强大的其他答案之一是具有模板方法的能力。方法可以是基本枚举的一部分,并且可以按每种类型覆盖。并且,由于此行为附加到枚举,因此通常消除了对此博客文章所演示的 if-else构造或switch语句的需要-在enum.method()
条件内最初将执行什么操作。相同的示例还显示了将静态导入与枚举一起使用,以及生成更清晰的DSL之类的代码。
其他一些有趣的特质包括事实枚举提供实施equals()
,toString()
并hashCode()
贯彻Serializable
和Comparable
。
要完整地列举所有枚举,我强烈推荐Bruce Eckel的《Thinking in Java》第4版,其中整章专门介绍了该主题。带有枚举的涉及剪刀,石头,剪刀(即RoShamBo)游戏的示例尤其具有启发性。
从Java 文档 -
每当需要表示一组固定的常量时,都应使用枚举类型。其中包括自然的枚举类型,例如我们太阳系中的行星和数据集,您可以在编译时知道所有可能的值,例如,菜单上的选择,命令行标志等。
一个常见的示例是用带有枚举类型的一组私有静态最终int常量(在一定数量的常量内)替换一个类。基本上,如果您认为在编译时就知道“事物”的所有可能值,则可以将其表示为枚举类型。枚举为具有常量的类提供了可读性和灵活性。
我可以想到枚举类型的其他优点。它们始终是特定枚举类的一个实例(因此,在单例到达时使用枚举的概念)。另一个优点是,可以在switch-case语句中将枚举用作类型。您也可以在枚举上使用toString()将它们打印为可读字符串。
Enum
s以自我记录的方式枚举一组固定的值。
它们使您的代码更加明确,也减少了出错的可能性。
为什么不对常量使用String
或int
而不是Enum
?
if
)来确保您的参数在有效范围内。String
无论如何,您可能都需要使用类似数量的内存(这取决于的复杂度Enum
)。此外,每个Enum
的实例都是一个类,您可以为其定义各自的行为。
另外,它们在实例创建时(加载枚举时)确保线程安全,这在简化Singleton Pattern中已得到了广泛的应用。
该博客说明了其某些应用程序,例如解析器的状态机。
知道这enums
和其他带有Constant
field和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...
}
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
的静态方法来获取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
希望这可以帮助。
枚举继承了Object
class和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;
}
}
}
ENum代表“枚举类型”。它是一种数据类型,您可以自己定义一组固定的常数。
对类型安全使用枚举,这是一种语言功能,因此您通常会得到:
枚举可以具有方法,构造函数,甚至可以在枚举内使用枚举并将枚举与接口结合起来。
将枚举视为替换一组定义明确的int常量(Java从C / C ++继承)的类型,并在某些情况下替换位标志。
《有效的Java 2nd Edition》一书中有关于它们的整章内容,并涉及更多细节。另请参阅此Stack Overflow帖子。
枚举?为什么要使用它?我认为您将在何时使用它。我有相同的经验。
假设您具有创建,删除,编辑和读取数据库的操作。
现在,如果您将枚举创建为操作:
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)在Switch-case
声明中用作常量。切换大小写将不允许您使用String对象作为大小写。枚举派上用场。更多:http : //www.javabeat.net/2009/02/how-to-use-enum-in-switch/
2)实施Singleton Design Pattern
-再次枚举,进行救援。用法,这里:在Java中将枚举用作单例的最佳方法是什么?
让我感到惊奇的是这一认识: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);
}
}
对于我来说,使代码在将来更具可读性,下一个代码段代表了最有用的枚举案例:
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;
}
}
};
根据我的经验,我发现使用Enum有时会导致很难更改系统。如果将枚举用于一组经常更改的特定于域的值,并且它依赖于它的许多其他类和组件,则可能要考虑不使用枚举。
例如,一个使用Enum进行市场/交易的交易系统。那里有很多市场,几乎可以肯定,会有很多子系统需要访问此市场列表。每当您希望将新市场添加到系统中时,或者如果您要删除市场,则可能必须重新构建和释放所有在阳光下的东西。
一个更好的例子是产品类别类型。假设您的软件管理百货商店的库存。有很多产品类别,并且有很多原因可以更改此类别列表。经理可能想要库存新的产品线,摆脱其他产品线,并可能不时重组类别。如果仅由于用户想要添加产品类别而不得不重建和重新部署所有系统,那么您所采取的措施应该既简单又快速(添加类别),并且变得非常困难和缓慢。
最重要的是,如果您所代表的数据随着时间的推移非常静态并且依赖项数量有限,则枚举会很好。但是,如果数据变化很大并且具有很多依赖性,那么您需要在编译时就不会检查的动态内容(例如数据库表)。
现代看待一个老问题
该方法通过利用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
)的多例图案代替确保单个实例每个键。详细说明每个都太冗长,因此我只链接了一篇好文章- 您想了解的有关Singleton的全部信息
我会使用枚举作为有用的映射工具,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
。
除了@BradB回答:
的确如此……很奇怪,这是唯一提到这一点的答案。当初学者发现枚举时,他们会迅速将其视为对编译器进行有效标识符检查的魔术。当打算在分布式系统上使用该代码时,他们哭了……一个月后。保持与包含非静态值列表的枚举的向后兼容性是一个令人担忧的难题。这是因为当您将值添加到现有枚举时,其类型会更改(尽管名称没有更改)。
“ Ho,等等,它看起来像是相同的类型,对吧?毕竟,它们是具有相同名称的枚举–枚举不就是幕后的整数吗?” 出于这些原因,您的编译器可能不会在期望其他类型的地方标记该类型本身的一种定义。但实际上,它们(以最重要的方式)是不同的类型。最重要的是,它们具有不同的数据域-值对于给定的类型是可以接受的。通过添加值,我们有效地更改了枚举的类型,因此破坏了向后兼容性。
结论:可以在需要时使用它,但是请检查使用的数据域是否是有限的,已知的固定集。