java 基于enum的单例模式的实现,以及浅析如何确保其安全性

提示:如需转载,必须注明本文链接


前言

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的唯一性。所以,通过枚举实现单例模式是非常安全的。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值