How to use java enumeration efficiently and elegantly

background

The status of enumeration in the system is self-evident, status, type, scene, logo, etc., ranging from a dozen to hundreds. I believe the following code is very common, and similar codes are everywhere. The goal : Eliminate such redundant code.


/**
     * 根据枚举代码获取枚举
     * 
     */
    public static OrderStatus getByCode(String code){
    
    
        for (OrderStatus v : values()) {
    
    
            if (v.getCode().equals(code)) {
    
    
                return v;
            }
        }
        return null;
    }

    /**
     * 根据枚举名称获取枚举
     * 当枚举内的实例数越多时性能越差
     */
    public static OrderStatus getByName(String name){
    
    
        for (OrderStatus v : values()) {
    
    
            if (v.name().equals(name)) {
    
    
                return v;
            }
        }
        return null;
    }

enum cache

  • Reduce code redundancy and concise code
  • Remove the for loop, the performance is stable and efficient

Module Design DrawingPlease add a picture description

cache structure

Please add a picture description

Source code analysis


package com.alipay.enumcache;

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

/**
 * 枚举缓存
 */
public class EnumCache {
    
    

    /**
     * 以枚举任意值构建的缓存结构
     **/
    static final Map<Class<? extends Enum>, Map<Object, Enum>> CACHE_BY_VALUE = new ConcurrentHashMap<>();
    /**
     * 以枚举名称构建的缓存结构
     **/
    static final Map<Class<? extends Enum>, Map<Object, Enum>> CACHE_BY_NAME = new ConcurrentHashMap<>();
    /**
     * 枚举静态块加载标识缓存结构
     */
    static final Map<Class<? extends Enum>, Boolean> LOADED = new ConcurrentHashMap<>();


    /**
     * 以枚举名称构建缓存,在枚举的静态块里面调用
     *
     * @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);
    }

    /**
     * 以枚举转换出的任意值构建缓存,在枚举的静态块里面调用
     *
     * @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已经构建过value缓存,不允许重复构建", 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映射同一个枚举%s.%s", clazz.getSimpleName(), value, clazz.getSimpleName(), e));
            }
            map.put(value, e);
        }
        CACHE_BY_VALUE.put(clazz, map);
    }

    /**
     * 从以枚举名称构建的缓存中通过枚举名获取枚举
     *
     * @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);
    }

    /**
     * 从以枚举转换值构建的缓存中通过枚举转换值获取枚举
     *
     * @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);// 触发枚举静态块执行
            map = cache.get(clazz);// 执行枚举静态块后重新获取缓存
        }
        if (map == null) {
    
    
            String msg = null;
            if (cache == CACHE_BY_NAME) {
    
    
                msg = String.format(
                        "枚举%s还没有注册到枚举缓存中,请在%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还没有注册到枚举缓存中,请在%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块运行,static块没有执行完是会阻塞在此的
                        Class.forName(clazz.getName());
                        LOADED.put(clazz, true);
                    } catch (Exception e) {
    
    
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

    /**
     * 枚举缓存映射器函数式接口
     */
    @FunctionalInterface
    public interface EnumMapping<E extends Enum> {
    
    
        /**
         * 自定义映射器
         *
         * @param e 枚举
         * @return 映射关系,最终体现到缓存中
         */
        Object value(E e);
    }

}

key interpretation

  1. The Open-Closed Principle
    What is the Open-Closed Principle?
    It is closed for modification and open for new extensions. In order to meet the principle of opening and closing, it is designed that enumerations are actively registered to the cache instead of caches actively loading enumerations. The advantage of this design is that when adding an enumeration, it only needs to be registered independently in the static block of the current enumeration That's it, no need to modify other codes.
    For example, we now want to add a new state class enumeration:

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 {
    
    
        // 通过名称构建缓存,通过EnumCache.findByName(StatusEnum.class,"SUCCESS",null);调用能获取枚举
        EnumCache.registerByName(StatusEnum.class, StatusEnum.values());
        // 通过code构建缓存,通过EnumCache.findByValue(StatusEnum.class,"S",null);调用能获取枚举
        EnumCache.registerByValue(StatusEnum.class, StatusEnum.values(), StatusEnum::getCode);
    }
}
  1. Registration timing
    Put the registration in the static block, so when will the static block be executed?

1. When a new instance of a class is created for the first time
2. When any static method of a class is called for the first time
3. When any non-final static field of a class or interface is used for the first time
4. When first Class.forName

If we enter StatusEnum to create an enumeration, then the static block of StatusEnum may never be executed during the application system startup process, and the enumeration cache registration fails, so we need to consider delayed registration, the code is as follows:

private static <E extends Enum> void executeEnumStatic(Class<E> clazz) {
    
    
        if (!LOADED.containsKey(clazz)) {
    
    
            synchronized (clazz) {
    
    
                if (!LOADED.containsKey(clazz)) {
    
    
                    try {
    
    
                        // 目的是让枚举类的static块运行,static块没有执行完是会阻塞在此的
                        Class.forName(clazz.getName());
                        LOADED.put(clazz, true);
                    } catch (Exception e) {
    
    
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }

Two prerequisites for Class.forName(clazz.getName()) to be executed:

1. There is no enumeration class key in the cache, that is to say, there is no call to enumerate and register with the cache. See the call of the executeEnumStatic method by the EnumCache.find method; 2.
LOADED.put(clazz, true) in executeEnumStatic; It has not been executed yet, that is, Class.forName(clazz.getName()); has not been executed;

We see that double-check locks are used in executeEnumStatic, so analyze the code execution and performance under normal circumstances:

  1. When the static block has not yet been executed, a large number of find queries are executed concurrently.

    • At this time, synchronized in executeEnumStatic will block other threads;
    • The first thread to get the lock will execute Class.forName(clazz.getName()); at the same time trigger the synchronous execution of the enumeration static block;
    • After that, other threads will get the locks one by one, and the second check will fail to jump out of executeEnumStatic;
  2. When the static block has been executed, and the cache registration is normally executed in the static block, a large number of find queries are executed concurrently.

    • The executeEnumStatic method will not be called, and there is no queuing problem caused by synchronized;
  3. When the static block has been executed, but the cache registration is not called in the static block, a large number of find queries are executed concurrently.

    • The find method will call the executeEnumStatic method, but the first check of executeEnumStatic fails;
    • The find method will prompt that the exception needs to add the code for registering the cache in the static block;

Summary: In the first scenario, there will be short-lived serialization, but this kind of short-lived serialization of memory computing is negligible compared to the execution of business logic in the application system, which means that this short-lived serialization will not become the performance bottleneck of the system

sample display

  • construct enumeration

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 {
    
    
        // 通过名称构建缓存,通过EnumCache.findByName(StatusEnum.class,"SUCCESS",null);调用能获取枚举
        EnumCache.registerByName(StatusEnum.class, StatusEnum.values());
        // 通过code构建缓存,通过EnumCache.findByValue(StatusEnum.class,"S",null);调用能获取枚举
        EnumCache.registerByValue(StatusEnum.class, StatusEnum.values(), StatusEnum::getCode);
    }
}
  • test class
public class Test{
    
    

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


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

  • Results of the

SUCCESS
INIT
INIT
SUCCESS
INIT
INIT

performance comparison

  • Comparing the code, if the number of instances in OrderType increases, the performance difference will be greater

public class Test {
    
    

    enum OrderType {
    
    
        _00("00", "00"),
        _01("01", "01"),
        _02("02", "02"),
        _03("03", "03"),
        _04("04", "04"),
        _05("05", "05"),
        _06("06", "06"),
        _07("07", "07"),
        _08("08", "08"),
        _09("09", "09"),
        _10("10", "10")
        ;
        private String code;
        private String desc;

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

        public String getCode() {
    
    
            return code;
        }

        public String getDesc() {
    
    
            return desc;
        }

        static {
    
    
            EnumCache.registerByValue(OrderType.class, OrderType.values(), OrderType::getCode);
        }

        public static OrderType getEnumByCode(String code, OrderType def) {
    
    
            OrderType[] values = OrderType.values();
            for (OrderType value : values) {
    
    
                if (value.getCode().equals(code)) {
    
    
                    return value;
                }
            }
            return def;
        }
    }

    private static final OrderType DEF = OrderType._00;
    private static final int TIMES = 10000000;

    static void compare(String code) {
    
    
        long s = System.currentTimeMillis();
        for (int idx = 0; idx < TIMES; idx++) {
    
    
            OrderType.getEnumByCode(code, DEF);
        }
        long t = System.currentTimeMillis() - s;
        System.out.println(String.format("枚举->%s : %s", code, t));

        s = System.currentTimeMillis();
        for (int idx = 0; idx < TIMES; idx++) {
    
    
            EnumCache.findByValue(OrderType.class, code, DEF);
        }
        t = System.currentTimeMillis() - s;
        System.out.println(String.format("缓存->%s : %s", code, t));
        System.out.println();
    }

    public static void main(String[] args) throws Exception {
    
    
        for (int idx = 0; idx < 2; idx++) {
    
    
            compare("NotExist");
            for (OrderType value : OrderType.values()) {
    
    
                compare(value.getCode());
            }
            System.out.println("=================");
        }
    }
}
  • Results of the

枚举->NotExist : 312
缓存->NotExist : 105

枚举->00 : 199
缓存->00 : 164

枚举->01 : 313
缓存->01 : 106

枚举->02 : 227
缓存->02 : 90

枚举->03 : 375
缓存->03 : 92

枚举->04 : 260
缓存->04 : 92

枚举->05 : 272
缓存->05 : 78

枚举->06 : 284
缓存->06 : 78

枚举->07 : 315
缓存->07 : 76

枚举->08 : 351
缓存->08 : 78

枚举->09 : 372
缓存->09 : 81

枚举->10 : 402
缓存->10 : 78

=================
枚举->NotExist : 199
缓存->NotExist : 68

枚举->00 : 99
缓存->00 : 91

枚举->01 : 141
缓存->01 : 79

枚举->02 : 178
缓存->02 : 77

枚举->03 : 202
缓存->03 : 77

枚举->04 : 218
缓存->04 : 81

枚举->05 : 259
缓存->05 : 90

枚举->06 : 322
缓存->06 : 78

枚举->07 : 318
缓存->07 : 78

枚举->08 : 347
缓存->08 : 77

枚举->09 : 373
缓存->09 : 79

枚举->10 : 404
缓存->10 : 78

=================

Summarize

  1. The code is concise;
  2. The greater the number of instances in the enumeration, the greater the performance benefit of cache mode.

Guess you like

Origin blog.csdn.net/dedede001/article/details/131303712