提示:如需转载,必须注明本文链接
前言
java开发中,我们常用enum实现来单例模式,并且不仅能避免多线程同步问题,而且能确保jvm级别的序列化反序列化的安全性。那么enum类型是如何保证这些呢?
一、enum是什么?
enum 的全称为 enumeration, 是 JDK 1.5 中引入的新特性。
在Java中,被 enum 关键字修饰的类型就是枚举类型。简单形式如下:
public enum MediaTypeEnum {
MP3,
MP4
}
我们执行 javap MediaTypeEnum.class ,看下反编译后的枚举类 代码:
1.MediaTypeEnum extends java.lang.Enum<test.MediaTypeEnum>
2.每个枚举类型都会默认被public static final 修饰
3.内部静态方法values()返回所有枚举类
4.valueOf()根据name匹配枚举类型综上,当我们使用enmu来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,因为java的单继承性质,以及默认枚举是final修饰。所以枚举类不能继承其他类,也不能被继承。但是可以实现接口类 。
Compiled from "MediaTypeEnum.java"
public final class test.MediaTypeEnum extends java.lang.Enum<test.MediaTypeEnum> {
public static final test.MediaTypeEnum MP3;
public static final test.MediaTypeEnum MP4;
public static test.MediaTypeEnum[] values();
public static test.MediaTypeEnum valueOf(java.lang.String);
static {};
}
每一个枚举元素都能返回当前枚举类型:
public static void main(String[] args) {
MediaTypeEnum playMedia = MediaTypeEnum.MP3;
System.out.println(playMedia); // MP3
}
二、基于enum单例模式的实现举例
如果仅仅是一些枚举类型,那对于单例模式来说是没有意义的。实际上它也能作为一种特殊Class去实现和继承其他接口、类,重写方法。这样,我们就可以利用这些方法自定义一些处理逻辑。
注:重写的方法是针对所有枚举实例,如下:
// 接口类
public interface MediaOpration {
String playMedia(int times);
}
public enum MediaTypeEnum implements MediaOpration {
MP3 {
// 可以有自己的属性
private int closeCount = 0;
@Override
void close() {
closeCount ++;
System.out.println(closeCount);
}
@Override
public String playMedia(int times) {
System.out.println("播放:" + this.name() + ",【" + times + "】次");
return this.name();
}
},
MP4 {
@Override
void close() {}
@Override
public String playMedia(int times) {
System.out.println("播放:" + this.name() + ",【" + times + "】次");
return this.name();
}
};
// 抽象方法
abstract void close();
public static void main(String[] args) {
System.out.println(MediaTypeEnum.MP3.hashCode());
MediaTypeEnum.MP3.close();
String playMedia = MediaTypeEnum.MP3.playMedia(3);
System.out.println(MediaTypeEnum.MP3.hashCode());
System.out.println(playMedia);
}
}
执行main方法结果:可以看到MediaTypeEnum.MP3.hashCode()一直是没有变化的,都是同一个实例:
237852351
1
播放:MP3,【3】次
237852351
MP3
三、单例模式是如何保证不被破坏的?
我们说的单例模式如果是普通类实现的,是通过以下方式来破坏单例模式:
1、采用反射方式来创建:
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
MediaTypeEnum mp3 = MediaTypeEnum.MP3;
// 反射方式,获取实例
MediaTypeEnum instance = mp3.getClass().getDeclaredConstructor(String.class, int.class).newInstance(); // 但是会报错
System.out.println(instance);
}
我们在此处的报错信息打开,发现newInstance()方法直接不让enum类型利用反射来创建实例。所以利用反射方式来破坏单例模式不可能。
2、实现java.lang.Cloneable接口,调用clone方法来创建:
MediaTypeEnum mp3 = MediaTypeEnum.MP3;
// 克隆方式
Object clone = mp3.clone();
我们调用enum枚举的时候实际上调用的是父类:java.lang.Enum里面的clone()。但是Enum的clone()也是直接抛出异常,如下。所以clone()方式也是不行的。
3、采用实现java.io.Serializable接口,反序列化方式:
在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象,不存在创建对象的可能。同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,所以此方式也行不通。
总结
综上,jvm能够有效的防止enum枚举被创建新的对象,保证enum的唯一性。所以,通过枚举实现单例模式是非常安全的。