java枚舉類及面試題爲什麼枚舉實現單例模式是安全的?

枚舉類

爲什麼會有枚舉類?

假如說程序中有一些顏色的狀態,或者說消息的類型等,在JDK1.5之前,只能用常量來進行表示

public class TestEnum {
    public static final int RED = 1;
    public static final int BLACK = 2;
    public static final int GREEN = 3;
    public static void main(String[] args) {
        int num = 1;
        System.out.println(RED == num);
    }
}
//true

num不是一個顏色,就與實際不太相符合,於是JDK1.5中引入了枚舉類型

enum EnumColor {
    RED, BLACK, GREEN;

    public static void main(String[] args) {
        EnumColor color = EnumColor.BLACK;
        switch (color) {
            case RED:
                System.out.println("紅色");
                break;
            case BLACK:
                System.out.println("黑色");
                break;
            case GREEN:
                System.out.println("綠色");
                break;
        }
    }
}
//黑色

枚舉類的使用

  • 類的對象只有有限個,確定的(星期,性別,季節…)
  • 當需要定義一組常量
  • 使用enum定義的枚舉類默認繼承了java.lang. Enum類,因此不能再繼承其他類
  • 枚舉類的構造器只能使用private權限修飾符
  • 枚舉類的所有實例必須在枚舉類中顯式列出(,分隔結尾)。列出的實例
    系統會自動添加public static final修飾必須在枚舉類的第一行聲明枚舉類對象
  • 枚舉不能再類外直接實例化,也不能被繼承

如果枚舉有參數可以添加對應的構造函數,但要注意:枚舉的構造函數默認是私有的。所以不能被繼承
枚舉就是定義了一些狀態或者常見集合,一般不需要單獨實例化,用的時候到枚舉類中找合適的即可
枚舉的構造函數不能使用public和protected修飾。

枚舉類的常用方法

方法名稱 描述
values() 以數組形式返回枚舉類型的所有成員
ordinal() 獲取枚舉成員的索引位置
valueOf() 將普通字符串轉換爲枚舉實例
compareTo() 比較兩個枚舉成員在定義時的順序
enum EnumColor {
    RED, BLACK, GREEN;
    public static void main(String[] args) {
    // 以數組的方式返回所有的枚舉成員
        EnumColor[] colors = EnumColor.values();
    // 打印每個枚舉成員的索引
        for(int i = 0; i < colors.length; ++i){
            System.out.println(colors[i] + ":" + colors[i].ordinal());
        }
    // 將字符串GREEN轉化爲枚舉類型
        EnumColor color1 = EnumColor.valueOf("GREEN");
        System.out.println(color1);
    // 在進行轉換時,如果有對應的枚舉類型則轉換,否則拋出IllegalArgumentException
//     EnumColor color2 = EnumColor.valueOf("YELLOW");//定義的枚舉類沒有YELLOW
//     System.out.println(color2);
        EnumColor color2 = EnumColor.valueOf("BLACK");//定義的枚舉類沒有YELLOW
        System.out.println(color2);
        System.out.println("-------------------------------------");
        System.out.println("枚舉實例的比較");
    // 注意此處的比較是使用枚舉成員的索引來比較了
        EnumColor black = EnumColor.BLACK;
        EnumColor red = EnumColor.RED;
        System.out.println(black.compareTo(red));
        System.out.println(BLACK.compareTo(RED));
        System.out.println(RED.compareTo(BLACK));
    }
    //結果:
    RED:0
	BLACK:1
	GREEN:2
	GREEN
	BLACK
	-------------------------------------
	枚舉實例的比較
	1
	1
	-1

枚舉的構造

上述枚舉類型有一個不太友好的地方是,枚舉中只有枚舉常量,拿到一個枚舉常量後還是不知道其是什麼顏色的,因此可以給枚舉增加構造函數。

 enum EnumColor {
    RED("紅色", 1), BLACK("黑色", 2), GREEN("綠色", 3);
    private String color;
    private int key;
    // 注意:枚舉的構造函數默認是私有的,此處不能增加非private的訪問權限進行限制
    /*public*/EnumColor(String color, int key){
        this.color = color;
        this.key = key;
    }
    public static EnumColor getEnum(String str){
        for (EnumColor color : EnumColor.values()){
            if(color.color.equals(str))
                return color;
        }
        return null;
    }
    public static void main(String[] args) {
        System.out.println(EnumColor.getEnum("紅色"));
    }
}

枚舉類型能被反射嗎?

不能不能不能!
通過看反射的源碼,可以看出反射在通過newInstance創建對象時,會檢查該類是否ENUM修飾,如果是則拋出異常,反射失敗。
在這裏插入圖片描述

爲什麼枚舉實現單例模式是安全的?

enum Singleton5 {//枚舉類的單例模式
    INSTANCE;
    public Singleton5 getInstance(){
        return INSTANCE;
    }
}

1、 私有化構造器並不保險,通過反射機制調用私有構造器。而枚舉類不能被反射,所以可以防止反射攻擊

//模擬反射攻擊
class Singleton {//雙重校驗鎖,性能佳線程安全
    private static Singleton4 instance=null;
    private Singleton4() {}
    public static Singleton4 getInstance() {
        if(instance==null) {
            synchronized (Singleton4.class) {
                if (instance==null) {
                    //new對象分爲三個操作
                    //分配內存
                    //調用構造方法初始化
                    //賦值
                    instance=new Singleton4();
                }
            }
        }
        return instance;
    }
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    Singleton s=Singleton.getInstance();
    Singleton sUsual=Singleton.getInstance();
    Constructor<Singleton> constructor=Singleton.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    Singleton sReflection=constructor.newInstance();
    System.out.println(s+"\n"+sUsual+"\n"+sReflection);
    System.out.println("正常情況下,實例化兩個實例是否相同:"+(s==sUsual));
    System.out.println("通過反射攻擊單例模式情況下,實例化兩個實例是否相同:"+(s==sReflection));
}
//com.lxp.pattern.singleton.Singleton@1540e19d
//com.lxp.pattern.singleton.Singleton@1540e19d
//.lxp.pattern.singleton.Singleton@677327b6
//正常情況下,實例化兩個實例是否相同:true
//通過反射攻擊單例模式情況下,實例化兩個實例是否相同:false

2.避免序列化問題(任何一個readObject方法,不管是顯式的還是默認的,它都會返回一個新建的實例,這個新建的實例不同於該類初始化時創建的實例)

枚舉類的優缺點

1、優點

枚舉常量更簡單安全 。
枚舉具有內置方法 ,代碼更優雅

2. 缺點

不可繼承,無法擴展
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章