背景
枚舉在系統(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í)行呢?
- 當(dāng)?shù)谝淮蝿?chuàng)建某個(gè)類的新實(shí)例時(shí)
- 當(dāng)?shù)谝淮握{(diào)用某個(gè)類的任意靜態(tài)方法時(shí)
- 當(dāng)?shù)谝淮问褂媚硞€(gè)類或接口的任意非final靜態(tài)字段時(shí)
- 當(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è)必備條件:
- 緩存中沒有枚舉class的鍵,也就是說沒有執(zhí)行過枚舉向緩存注冊(cè)的調(diào)用,見EnumCache.find方法對(duì)executeEnumStatic方法的調(diào)用;
- executeEnumStatic中的LOADED.put(clazz, true);還沒有被執(zhí)行過,也就是Class.forName(clazz.getName());沒有被執(zhí)行過;
我們看到executeEnumStatic中用到了雙重檢查鎖,所以分析一下正常情況下代碼執(zhí)行情況和性能:
- 當(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;
- 當(dāng)靜態(tài)塊已經(jīng)執(zhí)行,且靜態(tài)塊里面正常執(zhí)行了緩存注冊(cè),大量的并發(fā)執(zhí)行find查詢。
- executeEnumStatic方法不會(huì)調(diào)用,沒有synchronized引發(fā)的排隊(duì)問題;
- 當(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