Java枚举enum以及应⽤:枚举实现单例模式
枚举作为⼀个常规的语⾔概念,⼀直到Java5才诞⽣不得不说有点奇怪,以⾄于到现在为⽌很多程序员仍然更喜欢⽤static final的形式去命名常量⽽不使⽤,⼀般情况下,Java程序员⽤这种⽅式去实现枚举:
class EnumByClass{
public static final int RED=0;
public static final int GREEN=1;
public static final int BLUE=2;
}
这种⽅式实现的枚举也叫int枚举模式,尽管很常⽤,但是由int实现的枚举很难保证安全性,即当调⽤不在枚举范围内的数值时,需要额外的维护。另外,也不利于查看log和测试。
此时,我们需要开始使⽤Java的枚举类型,例如上⾯的int枚举模式类如果⽤enum实现,那么代码如下:
enum Color{
RED,GREEN,BLUE;
}
上述是将枚举作为常量集合的简单⽤法,实际上,枚举更多的还是⽤于switch,也是在这时才能发现枚举相对于int枚举模式的好处,这⾥⾯举⼀个⽤enum实现switch的例⼦:
enum Color{
RED,GREEN,BLUE;
}
public class Hello {
public static void main(String[] args){
Color color=Color.RED;
int counter=10;
while (counter-->0){
switch (color){
case RED:
System.out.println("Red");
color=Color.BLUE;
break;
case BLUE:单例模式的几种实现方式
System.out.println("Blue");
color=Color.GREEN;
break;
case GREEN:
System.out.println("Green");
color=Color.RED;
break;
}
}
}
}
如果我们⽤int枚举模式的话,诚然可以⽤⼀些类似++,——的语法糖,但是也要更多的考虑到安全性的问题。
如果仅此⽽已,那么枚举也没什么单独拿出来写博客的价值。
我个⼈对enum感兴趣主要是因为之前在介绍Singleton时有⼀个⾮常经验的枚举实现的单例,代码如下:
enum SingletonDemo{
INSTANCE;
public void otherMethods(){
System.out.println("Something");
}
}
简简单单的⼀点代码就实现了⼀个线程安全的单例,与其说是写法⿁斧神⼯,不如说是恰如其分地应⽤了enum的性质。
在⽤enum实现Singleton时我曾介绍过三个特性,⾃由序列化,线程安全,保证单例。这⾥我们就要探讨⼀下why的问题。
⾸先,我们都知道enum是由class实现的,换⾔之,enum可以实现很多class的内容,包括可以有member和member function,这也是我们可以⽤enum作为⼀个类来实现单例的基础。另外,由于enu
m是通过继承了Enum类实现的,enum结构不能够作为⼦类继承其他类,但是可以⽤来实现接⼝。此外,enum类也不能够被继承,在反编译中,我们会发现该类是final的。
其次,enum有且仅有private的构造器,防⽌外部的额外构造,这恰好和单例模式吻合,也为保证单例性做了⼀个铺垫。这⾥展开说下这个private构造器,如果我们不去⼿写构造器,则会有⼀个默认的空参构造器,我们也可以通过给枚举变量参量来实现类的初始化。这⾥举⼀个例⼦。
enum Color{
RED(1),GREEN(2),BLUE(3);
private int code;
Color(int code){
}
public int getCode(){
return code;
}
}
需要注意的是,private修饰符对于构造器是可以省略的,但这不代表构造器的权限是默认权限。
⽬前我们对enum的结构和特性有了初步的了解,接下来探究⼀下原理层次的特性。
想要了解enum是如何⼯作的,就要对其进⾏反编译。
反编译后就会发现,使⽤枚举其实和使⽤静态类内部加载⽅法原理类似。枚举会被编译成如下形式:
public final class T extends Enum{
...
}
其中,Enum是Java提供给编译器的⼀个⽤于继承的类。枚举量的实现其实是public static final T 类型
的未初始化变量,之后,会在静态代码中对枚举量进⾏初始化。所以,如果⽤枚举去实现⼀个单例,这样的加载时间其实有点类似于饿汉模式,并没有起到lazy-loading的作⽤。
对于序列化和反序列化,因为每⼀个枚举类型和枚举变量在JVM中都是唯⼀的,即Java在序列化和反序列化枚举时做了特殊的规定,枚举的writeObject、readObject、readObjectNoData、writeReplace和readResolve等⽅法是被编译器禁⽤的,因此也不存在实现序列化接⼝后调⽤readObject会破坏单例的问题。
对于线程安全⽅⾯,类似于普通的饿汉模式,通过在第⼀次调⽤时的静态初始化创建的对象是线程安全的。
因此,选择枚举作为Singleton的实现⽅式,相对于其他⽅式尤其是类似的饿汉模式主要有以下优点:
1. 代码简单
2. ⾃由序列化
⾄于lazy-loading,考虑到⼀般情况不存在调⽤单例类⼜不需要实例化单例的情况,所以即便不能做到很好的lazy-loading,也并不是⼤问题。换⾔之,除了枚举这种⽅案,饿汉模式也在单例设计中⼴泛的被应⽤。例如,Hibernate默认的单例,获取sessionFactory⽤的HibernateUtil类建⽴⽅式如下:
public class HibernateUtil {
private static final SessionFactory ourSessionFactory;
static {
try {
Configuration configuration = new Configuration();
ourSessionFactory = configuration.buildSessionFactory();
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
public static Session getSession() throws HibernateException {
return ourSessionFactory.openSession();
}
}
这是⼀个典型的饿汉模式,考虑到这个单例只有⼀个⽅法即getSession,显然这种模式本⾝就是最优的且简洁的。这⾥⾯由于SessionFactory的创建并不是⽤系统默认的⽅式,如果想要⽤enum去实现反⽽⿇烦且⽆必要。不过⾄少说明这样做也许需要⼀个解决⾃由序列化的问题。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论