枚举的高级使用方法
就像我们前面的案例一样,你需要让每一个星期几对应到一个整数,比如星期天对应0。上面讲到了,枚举类在定义的时候会自动为每个变量添加一个顺序,从0开始。
假如你希望0代表星期天,1代表周一。。。并且你在定义枚举类的时候,顺序也是这个顺序,那你可以不用定义新的变量,就像这样:
public enum Weekday { SUN,MON,TUS,WED,THU,FRI,SAT }
这个时候,星期天对应的ordinal值就是0,周一对应的就是1,满足你的要求。但是,如果你这么写,那就有问题了:
public enum Weekday { MON,TUS,WED,THU,FRI,SAT,SUN }
我吧SUN放到了最后,但是我还是希0代表SUN,1代表MON怎么办呢?默认的ordinal是指望不上了,因为它只会傻傻的给第一个变量0,给第二个1。。。
所以,我们需要自己定义变量!
看代码:
我们对上面的代码做了一些改变:
首先,我们在每个枚举变量的后面加上了一个括号,里面是我们希望它代表的数字。
然后,我们定义了一个int变量,然后通过构造函数初始化这个变量。
你应该也清楚了,括号里的数字,其实就是我们定义的那个int变量。这句叫做自定义变量。
请注意:这里有三点需要注意:
一定要把枚举变量的定义放在第一行,并且以分号结尾。
构造函数必须私有化。事实上,private是多余的,你完全没有必要写,因为它默认并强制是private,如果你要写,也只能写private,写public是不能通过编译的。
自定义变量与默认的ordinal属性并不冲突,ordinal还是按照它的规则给每个枚举变量按顺序赋值。
好了,你很聪明,你已经掌握了上面的知识,你想,既然能自定义一个变量,能不能自定义两个呢?
当然可以:
你可以定义任何你想要的变量。学完了这些,大概枚举类你也应该掌握了,但是,还有没有其他用法呢?
枚举类中的抽象类
如果我在枚举类中定义一个抽象方法会怎么样?
你要知道,枚举类不能继承其他类,也不能被其他类继承。至于为什么,我们后面会说到。
你应该知道,有抽象方法的类必然是抽象类,抽象类就需要子类继承它然后实现它的抽象方法,但是呢,枚举类不能被继承。。你是不是有点乱?
我们先来看代码:
你好像懂了点什么。但是你好像又不太懂。为什么一个变量的后边可以带一个代码块并且实现抽象方法呢?
别着急,带着这个疑问,我们来看一下枚举类的实现原理。
枚举类的实现原理
从最简单的看起:
还是这段熟悉的代码,我们编译一下它,再反编译一下看看它到底是什么样子的:
你是不是觉得很熟悉?反编译出来的代码和我们用静态变量自己写的类出奇的相似!
而且,你看到了熟悉的values()方法和valueOf()方法。
仔细看,这个类继承了java.lang.Enum类!所以说,枚举类不能再继承其他类了,因为默认已经继承了Enum类。
并且,这个类是final的!所以它不能被继承!
回到我们刚才的那个疑问:
为什么会有这么神奇的代码?现在你差不多懂了。因为RED本身就是一个TrafficLamp对象的引用。实际上,在初始化这个枚举类的时候,你可以理解为执行的是TrafficLamp RED = new TrafficLamp(30) ,但是因为TrafficLamp里面有抽象方法,还记得匿名内部类么?
我们可以这样来创建一个TrafficLamp引用:
而在枚举类中,我们只需要像上面那样写【RED(30){}】就可以了,因为java会自动的去帮我们完成这一系列操作
枚举类用法
虽然枚举类不能继承其他类,但是还是可以实现接口的
使用枚举创建单例模式
使用枚举创建的单例模式:
public enum EasySingleton{ INSTANCE; }
代码就这么简单,你可以使用EasySingleton.INSTANCE调用它,比起你在单例中调用getInstance()方法容易多了。
我们来看看正常情况下是怎样创建单例模式的:
用双检索实现单例:
下面的代码是用双检索实现单例模式的例子,在这里getInstance()方法检查了两次来判断INSTANCE是否为null,这就是为什么叫双检索的原因,记住双检索在java5之前是有问题的,但是java5在内存模型中有了volatile变量之后就没问题了。
public class DoubleCheckedLockingSingleton{ private volatile DoubleCheckedLockingSingleton INSTANCE; private DoubleCheckedLockingSingleton(){} public DoubleCheckedLockingSingleton getInstance(){ if(INSTANCE == null){ synchronized(DoubleCheckedLockingSingleton.class){ //double checking Singleton instance if(INSTANCE == null){ INSTANCE = new DoubleCheckedLockingSingleton(); } } } return INSTANCE; } }
你可以访问DoubleCheckedLockingSingleTon.getInstance()来获得实例对象。
用静态工厂方法实现单例:
public class Singleton{ private static final Singleton INSTANCE = new Singleton(); private Singleton(){} public static Singleton getSingleton(){ return INSTANCE; } }
你可以调用Singleton.getInstance()方法来获得实例对象。
上面的两种方式就是懒汉式和恶汉式单利的创建,但是无论哪一种,都不如枚举来的方便。而且传统的单例模式的另外一个问题是一旦你实现了serializable接口,他们就不再是单例的了。但是枚举类的父类【Enum类】实现了Serializable接口,也就是说,所有的枚举类都是可以实现序列化的,这也是一个优点。
总结
- 可以创建一个enum类,把它看做一个普通的类。除了它不能继承其他类了。(java是单继承,它已经继承了Enum),可以添加其他方法,覆盖它本身的方法
- switch()参数可以使用enum
- values()方法是编译器插入到enum定义中的static方法,所以,当你将enum实例向上转型为父类Enum是,values()就不可访问了。解决办法:在Class中有一个getEnumConstants()方法,所以即便Enum接口中没有values()方法,我们仍然可以通过Class对象取得所有的enum实例
无法从enum继承子类,如果需要扩展enum中的元素,在一个接口的内部,创建实现该接口的枚举,以此将元素进行分组。达到将枚举元素进行分组。
enum允许程序员为eunm实例编写方法。所以可以为每个enum实例赋予各自不同的行为。









