Coder,我懷疑你並不會枚舉

枚舉是JDK1.5引入的新特性。被enum關鍵字修飾的類就是一個枚舉類。java

關於枚舉,阿里巴巴開發手冊有這樣兩條建議:web

  1. 枚舉類名帶上 Enum 後綴,枚舉成員名稱須要全大寫,單詞間用下劃線隔開。算法

  2. 若是變量值僅在一個固定範圍內變化用 enum 類型來定義。sql

一 枚舉類有哪些特色

建立一個ColorEnum的枚舉類,經過編譯,再反編譯看看它發生了哪些變化。小程序

public enum ColorEnum {
    RED,GREEN,BULE;
}

使用命令javac ColorEnum.java進行編譯生成class文件,而後再用命令javap -p ColorEnum.class進行反編譯。數組

去掉包名,反編譯後的內容以下:安全

public final class ColorEnum extends Enum{
    public static final ColorEnum GREEN;
    public static final ColorEnum BULE;
    private static final ColorEnum[] $VALUES;
    public static ColorEnum[] values();
    public static ColorEnum valueOf(java.lang.String);
    private ColorEnum();
    static {};
}
  1. 枚舉類被final修飾,所以枚舉類不能被繼承;微信

  2. 枚舉類默認繼承了Enum類,java不支持多繼承,所以枚舉類不能繼承其餘類;app

  3. 枚舉類的構造器是private修飾的,所以其餘類不能經過構造器來獲取對象;ide

  4. 枚舉類的成員變量是static修飾的,能夠用類名.變量來獲取對象;

  5. values()方法是獲取全部的枚舉實例;

  6. valueOf(java.lang.String)是根據名稱獲取對應的實例;

二 枚舉建立線程安全的單例模式

public enum  SingletonEnum {

    INSTANCE;

    public void doSomething(){
        // dosomething...
    }
}

這樣一個單例模式就建立好了,經過SingletonEnum.INSTANCE來獲取對象就能夠了。

2.1 序列化形成單例模式不安全

一個類若是若是實現了序列化接口,則可能破壞單例。每次反序列化一個序列化的一個實例對象都會建立一個新的實例。

枚舉序列化是由JVM保證的,每個枚舉類型和定義的枚舉變量在JVM中都是惟一的,在枚舉類型的序列化和反序列化上,Java作了特殊的規定:在序列化時Java僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候則是經過java.lang.EnumvalueOf方法來根據名字查找枚舉對象。同時,編譯器是不容許任何對這種序列化機制的定製的並禁用了writeObjectreadObjectreadObjectNoDatawriteReplacereadResolve等方法,從而保證了枚舉實例的惟一性。

2.2 反射形成單例模式不安全

經過反射強行調用私有構造器來生成實例對象,形成單例模式不安全。

Class<?> aClass = Class.forName("xx.xx.xx");
Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);
SingletonEnum singleton = (SingletonEnum) constructor.newInstance("Java旅途");

可是使用枚舉建立的單例徹底不用考慮這個問題,來看看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;
}

若是是enum類型,則直接拋出異常Cannot reflectively create enum objects,沒法經過反射建立實例對象!

三 經過枚舉消除if/else

假如要寫一套加密接口,分別給小程序、app和web端來使用,可是這三種客戶端的加密方式不同。通常狀況下咱們會傳一個類型type來判斷來源,而後調用對應的解密方法便可。代碼以下:

if("WEIXIN".equals(type)){
    // dosomething
}else if("APP".equals(type)){
    // dosomething
}else if("WEB".equals(type)){
    // dosomething
}

如今使用枚舉來消除這些if/else。

寫一個加密用的接口,有加密和解密兩個方法。而後用不一樣的算法去實現這個接口完成加解密。

public interface Util {

    // 解密
    String decrypt();

    // 加密
    String encrypt();
}

建立一個枚舉類來實現這個接口

public enum UtilEnum implements Util {

    WEIXIN {
        @Override
        public String decrypt() {
            return "微信解密";
        }

        @Override
        public String encrypt() {
            return "微信加密";
        }
    },
    APP {
        @Override
        public String decrypt() {
            return "app解密";
        }

        @Override
        public String encrypt() {
            return "app加密";
        }
    },
    WEB {
        @Override
        public String decrypt() {
            return "web解密";
        }

        @Override
        public String encrypt() {
            return "web加密";
        }
    };
}

最後,獲取到type後,直接調用解密方法就好了。

String decryptMessage = UtilEnum.valueOf(type).decrypt();

之後,若是新增了一個其餘加密方式,只須要修改上面的枚舉類就完成了,業務代碼都不須要改動。

這就是枚舉類比較高級的兩個用法。


< END >

往期精選
  揭開鏈表的真面目
  揭開數組的真面目
  聊聊Mysql中的int(1)
  如何有效防止SQL注入攻擊
《RabbitMQ》如何保證消息不被重複消費
《RabbitMQ》如何保證消息的可靠性

本文分享自微信公衆號 - Java旅途(Javatrip)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索