1. 程式人生 > 其它 >最安全的單例模式-列舉

最安全的單例模式-列舉

技術標籤:設計模式設計模式

列舉實現

首先我們先看一下列舉實現單例模式


public enum EnumSingleton {
    INSTANCE;

    // 列舉能夠絕對有效的防止例項化多次,和防止反射和序列化破解
    public void add() {
        System.out.println("add方法...");
    }
}

Test類


public class Test {
    public static void main(String[] args) {
        EnumSingleton instance1 =
EnumSingleton.INSTANCE; EnumSingleton instance2 = EnumSingleton.INSTANCE; System.out.println(instance1==instance2); } }

我們看一下輸出結果
在這裡插入圖片描述

列舉的安全性

1、上一篇部落格中提到了我們可以通過JAVA的反射機制去破解我們的單例模式
https://blog.csdn.net/weixin_45498245/article/details/112742030

2、我們可以用反射去破解一下列舉

public class Test {
    public
static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException { Class<?> aClass = Class.forName("單例模式.列舉實現單例.EnumSingleton"); Constructor<?> declaredConstructor = aClass.
getDeclaredConstructor(); declaredConstructor.setAccessible(true);//設定構造許可權為可以訪問 EnumSingleton enumSingleton= (EnumSingleton) declaredConstructor.newInstance();//Singleton2 with modifiers "private" System.out.println(enumSingleton); } }

Exception in thread “main” java.lang.NoSuchMethodException: 單例模式.列舉實現單例.EnumSingleton.()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at 單例模式.破解單例.Test.main(Test.java:26)

這是為什麼呢?我們接著往下看列舉的原始碼分析

列舉原始碼分析

結果如下,這是為什麼呢 我們通過反編譯工具可以看一下列舉的程式碼(這是通過反編譯工具去反編譯了一下我們上邊的EnumSingleton.class)

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingleton.java

package 53554F8B6A215F0F.679A4E3E5B9E73B053554F8B;

import java.io.PrintStream;

public final class EnumSingleton extends Enum//可以看出我們的列舉無非也是一個類去實現了一個Enum父類
{

    public static EnumSingleton[] values()
    {
        return (EnumSingleton[])$VALUES.clone();
    }
	//根據我們列舉中定義的名字去拿類
    public static EnumSingleton valueOf(String name)
    {
        return (EnumSingleton)Enum.valueOf(53554F8B6A215F0F/679A4E3E5B9E73B053554F8B/EnumSingleton, name);
    }
	//有參構造,這裡可以發現列舉是沒有預設的無參構造的 i是一個序號
    private EnumSingleton(String s, int i)
    {
        super(s, i);
    }

    public void add()
    {
        System.out.println("add\u65B9\u6CD5...");
    } 
    private static final EnumSingleton $VALUES[]; //存放我們物件的陣列,然後去初始化
		//可以看到列舉的話是在靜態程式碼塊中去初始化的
    static 
    {
        INSTANCE = new EnumSingleton("INSTANCE", 0);
        $VALUES = (new EnumSingleton[] {
            INSTANCE
        });
    }
}

看完原始碼之後那麼我們通過反射去測試載入有參構造(因為通過上邊我們看出來列舉是沒有無參構造的,只有一個有參構造,那麼我們就通過反射對有參構造下手)

//有參構造去破解列舉的單例
public class Test {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
        Class<?> aClass = Class.forName("單例模式.列舉實現單例.EnumSingleton");
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class,int.class);//這次我們測試去破解有參構造
        declaredConstructor.setAccessible(true);//設定構造許可權為可以訪問
        EnumSingleton enumSingleton= (EnumSingleton) declaredConstructor.newInstance("123",1);//Singleton2 with modifiers "private"
        System.out.println(enumSingleton);
    }
}

結果如何呢?

Cannot reflectively create enum objects
會給我們報錯,意思是沒法去反射列舉類

為什麼呢?我們看一下反射中的newInstance原始碼:

 public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        // !!!這裡我們看到如果反射的類是列舉的話,會直接丟擲異常!!!
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

所以各位小夥伴明白為什麼列舉是最安全的了吧,反射機制的底層就規定了我們的列舉沒法進行反射獲取!