高效優(yōu)雅的使用Java枚舉

背景

枚舉在系統(tǒng)中的地位不言而喻,狀態(tài)、類型、場(chǎng)景、標(biāo)識(shí)等等,少則十幾個(gè)多則上百個(gè),相信以下這段代碼很常見,而且類似的代碼到處都是,目標(biāo):消除這類冗余代碼。


/**
     * 根據(jù)枚舉代碼獲取枚舉
     * 
     */
    public static OrderStatus getByCode(String code){
        for (OrderStatus v : values()) {
            if (v.getCode().equals(code)) {
                return v;
            }
        }
        return null;
    }

    /**
     * 根據(jù)枚舉名稱獲取枚舉
     * 當(dāng)枚舉內(nèi)的實(shí)例數(shù)越多時(shí)性能越差
     */
    public static OrderStatus getByName(String name){
        for (OrderStatus v : values()) {
            if (v.name().equals(name)) {
                return v;
            }
        }
        return null;
    }

枚舉緩存

減少代碼冗余,代碼簡(jiǎn)潔
去掉for循環(huán),性能穩(wěn)定高效

模塊設(shè)計(jì)圖

模塊設(shè)計(jì)圖.png

緩存結(jié)構(gòu)

緩存結(jié)構(gòu).png

源碼分析

源碼展示

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 枚舉緩存
 */
public class EnumCache {

    /**
     * 以枚舉任意值構(gòu)建的緩存結(jié)構(gòu)
     **/
    static final Map<Class<? extends Enum>, Map<Object, Enum>> CACHE_BY_VALUE = new ConcurrentHashMap<>();
    /**
     * 以枚舉名稱構(gòu)建的緩存結(jié)構(gòu)
     **/
    static final Map<Class<? extends Enum>, Map<Object, Enum>> CACHE_BY_NAME = new ConcurrentHashMap<>();
    /**
     * 枚舉靜態(tài)塊加載標(biāo)識(shí)緩存結(jié)構(gòu)
     */
    static final Map<Class<? extends Enum>, Boolean> LOADED = new ConcurrentHashMap<>();


    /**
     * 以枚舉名稱構(gòu)建緩存,在枚舉的靜態(tài)塊里面調(diào)用
     *
     * @param clazz
     * @param es
     * @param <E>
     */
    public static <E extends Enum> void registerByName(Class<E> clazz, E[] es) {
        Map<Object, Enum> map = new ConcurrentHashMap<>();
        for (E e : es) {
            map.put(e.name(), e);
        }
        CACHE_BY_NAME.put(clazz, map);
    }

    /**
     * 以枚舉轉(zhuǎn)換出的任意值構(gòu)建緩存,在枚舉的靜態(tài)塊里面調(diào)用
     *
     * @param clazz
     * @param es
     * @param enumMapping
     * @param <E>
     */
    public static <E extends Enum> void registerByValue(Class<E> clazz, E[] es, EnumMapping<E> enumMapping) {
        if (CACHE_BY_VALUE.containsKey(clazz)) {
            throw new RuntimeException(String.format("枚舉%s已經(jīng)構(gòu)建過value緩存,不允許重復(fù)構(gòu)建", clazz.getSimpleName()));
        }
        Map<Object, Enum> map = new ConcurrentHashMap<>();
        for (E e : es) {
            Object value = enumMapping.value(e);
            if (map.containsKey(value)) {
                throw new RuntimeException(String.format("枚舉%s存在相同的值%s映射同一個(gè)枚舉%s.%s", clazz.getSimpleName(), value, clazz.getSimpleName(), e));
            }
            map.put(value, e);
        }
        CACHE_BY_VALUE.put(clazz, map);
    }

    /**
     * 從以枚舉名稱構(gòu)建的緩存中通過枚舉名獲取枚舉
     *
     * @param clazz
     * @param name
     * @param defaultEnum
     * @param <E>
     * @return
     */
    public static <E extends Enum> E findByName(Class<E> clazz, String name, E defaultEnum) {
        return find(clazz, name, CACHE_BY_NAME, defaultEnum);
    }

    /**
     * 從以枚舉轉(zhuǎn)換值構(gòu)建的緩存中通過枚舉轉(zhuǎn)換值獲取枚舉
     *
     * @param clazz
     * @param value
     * @param defaultEnum
     * @param <E>
     * @return
     */
    public static <E extends Enum> E findByValue(Class<E> clazz, Object value, E defaultEnum) {
        return find(clazz, value, CACHE_BY_VALUE, defaultEnum);
    }

    private static <E extends Enum> E find(Class<E> clazz, Object obj, Map<Class<? extends Enum>, Map<Object, Enum>> cache, E defaultEnum) {
        Map<Object, Enum> map = null;
        if ((map = cache.get(clazz)) == null) {
            executeEnumStatic(clazz);// 觸發(fā)枚舉靜態(tài)塊執(zhí)行
            map = cache.get(clazz);// 執(zhí)行枚舉靜態(tài)塊后重新獲取緩存
        }
        if (map == null) {
            String msg = null;
            if (cache == CACHE_BY_NAME) {
                msg = String.format(
                        "枚舉%s還沒有注冊(cè)到枚舉緩存中,請(qǐng)?jiān)?s.static代碼塊中加入如下代碼 : EnumCache.registerByName(%s.class, %s.values());",
                        clazz.getSimpleName(),
                        clazz.getSimpleName(),
                        clazz.getSimpleName(),
                        clazz.getSimpleName()
                );
            }
            if (cache == CACHE_BY_VALUE) {
                msg = String.format(
                        "枚舉%s還沒有注冊(cè)到枚舉緩存中,請(qǐng)?jiān)?s.static代碼塊中加入如下代碼 : EnumCache.registerByValue(%s.class, %s.values(), %s::getXxx);",
                        clazz.getSimpleName(),
                        clazz.getSimpleName(),
                        clazz.getSimpleName(),
                        clazz.getSimpleName(),
                        clazz.getSimpleName()
                );
            }
            throw new RuntimeException(msg);
        }
        if(obj == null){
            return defaultEnum;
        }
        Enum result = map.get(obj);
        return result == null ? defaultEnum : (E) result;
    }

    private static <E extends Enum> void executeEnumStatic(Class<E> clazz) {
        if (!LOADED.containsKey(clazz)) {
            synchronized (clazz) {
                if (!LOADED.containsKey(clazz)) {
                    try {
                        // 目的是讓枚舉類的static塊運(yùn)行,static塊沒有執(zhí)行完是會(huì)阻塞在此的
                        Class.forName(clazz.getName());
                        LOADED.put(clazz, true);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

    /**
     * 枚舉緩存映射器函數(shù)式接口
     */
    @FunctionalInterface
    public interface EnumMapping<E extends Enum> {
        /**
         * 自定義映射器
         *
         * @param e 枚舉
         * @return 映射關(guān)系,最終體現(xiàn)到緩存中
         */
        Object value(E e);
    }

}

關(guān)鍵解讀

開閉原則

什么是開閉原則?
對(duì)修改是封閉的,對(duì)新增擴(kuò)展是開放的。為了滿足開閉原則,這里設(shè)計(jì)成有枚舉主動(dòng)注冊(cè)到緩存,而不是有緩存主動(dòng)加載枚舉,這樣設(shè)計(jì)的好處就是:當(dāng)增加一個(gè)枚舉時(shí)只需要在當(dāng)前枚舉的靜態(tài)塊中自主注冊(cè)即可,不需要修改其他的代碼
比如我們現(xiàn)在要新增一個(gè)狀態(tài)類枚舉:

public enum StatusEnum {
    INIT("I", "初始化"),
    PROCESSING("P", "處理中"),
    SUCCESS("S", "成功"),
    FAIL("F", "失敗");

    private String code;
    private String desc;

    StatusEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public String getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }

    static {
        // 通過名稱構(gòu)建緩存,通過EnumCache.findByName(StatusEnum.class,"SUCCESS",null);調(diào)用能獲取枚舉
        EnumCache.registerByName(StatusEnum.class, StatusEnum.values());
        // 通過code構(gòu)建緩存,通過EnumCache.findByValue(StatusEnum.class,"S",null);調(diào)用能獲取枚舉
        EnumCache.registerByValue(StatusEnum.class, StatusEnum.values(), StatusEnum::getCode);
    }
}

注冊(cè)時(shí)機(jī)

將注冊(cè)放在靜態(tài)塊中,那么靜態(tài)塊什么時(shí)候執(zhí)行呢?

  1. 當(dāng)?shù)谝淮蝿?chuàng)建某個(gè)類的新實(shí)例時(shí)
  2. 當(dāng)?shù)谝淮握{(diào)用某個(gè)類的任意靜態(tài)方法時(shí)
  3. 當(dāng)?shù)谝淮问褂媚硞€(gè)類或接口的任意非final靜態(tài)字段時(shí)
  4. 當(dāng)?shù)谝淮蜟lass.forName時(shí)
    如果我們從StatusEnum創(chuàng)建枚舉,那么在應(yīng)用系統(tǒng)啟動(dòng)的過程中StatusEnum的靜態(tài)塊可能從未執(zhí)行過,則枚舉緩存注冊(cè)失敗,所有我們需要考慮延遲注冊(cè),代碼如下:
private static <E extends Enum> void executeEnumStatic(Class<E> clazz) {
    if (!LOADED.containsKey(clazz)) {
        synchronized (clazz) {
            if (!LOADED.containsKey(clazz)) {
                try {
                    // 目的是讓枚舉類的static塊運(yùn)行,static塊沒有執(zhí)行完是會(huì)阻塞在此的
                    Class.forName(clazz.getName());
                    LOADED.put(clazz, true);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

Class.forName(clazz.getName())被執(zhí)行的兩個(gè)必備條件:

  1. 緩存中沒有枚舉class的鍵,也就是說沒有執(zhí)行過枚舉向緩存注冊(cè)的調(diào)用,見EnumCache.find方法對(duì)executeEnumStatic方法的調(diào)用;
  2. executeEnumStatic中的LOADED.put(clazz, true);還沒有被執(zhí)行過,也就是Class.forName(clazz.getName());沒有被執(zhí)行過;

我們看到executeEnumStatic中用到了雙重檢查鎖,所以分析一下正常情況下代碼執(zhí)行情況和性能:

  1. 當(dāng)靜態(tài)塊還未執(zhí)行時(shí),大量的并發(fā)執(zhí)行find查詢。
  • 此時(shí)executeEnumStatic中synchronized會(huì)阻塞其他線程;
  • 第一個(gè)拿到鎖的線程會(huì)執(zhí)行Class.forName(clazz.getName());同時(shí)觸發(fā)枚舉靜態(tài)塊的同步執(zhí)行;
  • 之后其他線程會(huì)逐一拿到鎖,第二次檢查會(huì)不成立跳出executeEnumStatic;
  1. 當(dāng)靜態(tài)塊已經(jīng)執(zhí)行,且靜態(tài)塊里面正常執(zhí)行了緩存注冊(cè),大量的并發(fā)執(zhí)行find查詢。
  • executeEnumStatic方法不會(huì)調(diào)用,沒有synchronized引發(fā)的排隊(duì)問題;
  1. 當(dāng)靜態(tài)塊已經(jīng)執(zhí)行,但是靜態(tài)塊里面沒有調(diào)用緩存注冊(cè),大量的并發(fā)執(zhí)行find查詢。
  • find方法會(huì)調(diào)用executeEnumStatic方法,但是executeEnumStatic的第一次檢查通不過;
  • find方法會(huì)提示異常需要在靜態(tài)塊中添加注冊(cè)緩存的代碼;

總結(jié):第一種場(chǎng)景下會(huì)有短暫的串行,但是這種內(nèi)存計(jì)算短暫串行相比應(yīng)用系統(tǒng)的業(yè)務(wù)邏輯執(zhí)行是微不足道的,也就是說這種短暫的串行不會(huì)成為系統(tǒng)的性能瓶頸

舉個(gè)??

枚舉類

public enum StatusEnum {
    INIT("I", "初始化"),
    PROCESSING("P", "處理中"),
    SUCCESS("S", "成功"),
    FAIL("F", "失敗");

    private String code;
    private String desc;

    StatusEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public String getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }

    static {
        // 通過名稱構(gòu)建緩存,通過EnumCache.findByName(StatusEnum.class,"SUCCESS",null);調(diào)用能獲取枚舉
        EnumCache.registerByName(StatusEnum.class, StatusEnum.values());
        // 通過code構(gòu)建緩存,通過EnumCache.findByValue(StatusEnum.class,"S",null);調(diào)用能獲取枚舉
        EnumCache.registerByValue(StatusEnum.class, StatusEnum.values(), StatusEnum::getCode);
    }
}

測(cè)試類

public class Test{

    public static void main(String [] args){
        System.out.println(EnumCache.findByName(StatusEnum.class, "SUCCESS", null));
        // 返回默認(rèn)值StatusEnum.INIT
        System.out.println(EnumCache.findByName(StatusEnum.class, null, StatusEnum.INIT));
        // 返回默認(rèn)值StatusEnum.INIT
        System.out.println(EnumCache.findByName(StatusEnum.class, "ERROR", StatusEnum.INIT));


        System.out.println(EnumCache.findByValue(StatusEnum.class, "S", null));
        // 返回默認(rèn)值StatusEnum.INIT
        System.out.println(EnumCache.findByValue(StatusEnum.class, null, StatusEnum.INIT));
        // 返回默認(rèn)值StatusEnum.INIT
        System.out.println(EnumCache.findByValue(StatusEnum.class, "ERROR", StatusEnum.INIT));
    }
}

執(zhí)行結(jié)果

SUCCESS
INIT
INIT
SUCCESS
INIT
INIT
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容