Java基础篇(12)—枚举类(枚举值对象)

JAVA && Spring && SpringBoot2.x — 学习目录

一 枚举值不是常量而是对象

1.1 枚举值的构造函数

枚举类经常被用来存储一组有限个数的候选常量。我们一般用其取代静态常量,如下列源码所示:

public enum HttpMethod {
    GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
 }

但是枚举类的作用就仅仅如此吗?

枚举类不仅仅具有静态常量的功能,你可以将枚举值看做为一个对象。它也会拥有面向对象编程的绝大多数功能。

public enum ResponseCodeEnum {

    /**
     * 枚举值对象
     */
    SUCCESS("0000", "交易成功"),
    FAIL("9999", "交易失败");
    
    private String code;
    private String message;

    ResponseCodeEnum(String code, String message) {
        this.code = code;
        this.message = message;
    }
}

创建枚举值对象对象时,实际上调用了ResponseCodeEnum(String code, String message)构造方法。

1.2 枚举值的具体方法

而在枚举类中定义的方法,枚举值对象都可以调用。枚举值对象(调用者)本身是作为this。

案例:枚举对象实现工厂模式:

public enum CarFactory {

    //定义可以生产汽车的类型
    FordCar, BuickCar;

    public Car create() {
        switch (this) {
            case FordCar:
                return new FordCar();
            case BuickCar:
                return new BuickCar();
            default:
                throw new AssertionError("无效参数");
        }
    }
}
 @Test
    public void testStaticFactory() {
        Car car = CarFactory.FordCar.create();
        System.out.println(car.name());
    }

调用枚举值对象的create()方法便可创建出对象,并且枚举值对象作为调用者,在方法中便可拿到this(CarFactory )类型。

1.3 枚举值的抽象方法

在枚举类中定义抽象方法,那么每个枚举值必须实现该方法。

public enum AbstractCarFactory {
    FordCar {
        @Override
        public Car create() {
            return new FordCar();
        }
    },
    BuickCar {
        @Override
        public Car create() {
            return new BuickCar();
        }
    };
    //抽象的生产方法
    public abstract Car create();
}
  @Test
    public void testAbstractFactory(){
        Car car = AbstractCarFactory.BuickCar.create();
        System.out.println(car.name());
    }

可以将枚举类看做一个父类,每个枚举值为子类。

二 枚举类使用技巧

2.1 校验字段是否在枚举类中

方式一:使用工具类

工具类:org.apache.commons.lang3.EnumUtils

import org.apache.commons.lang3.EnumUtils;

EnumUtils.isValidEnum(MyEnum.class, myValue)

查询myValue值是否在枚举类MyEnum中。

方式二:枚举类中增加方法

源码:org.springframework.http.HttpMethod

public enum HttpMethod {

    GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
        
    private static final Map<String, HttpMethod> mappings = new HashMap<>(16);
   //初始化类时,将枚举值name和枚举类对象放入map中
    static {
        for (HttpMethod httpMethod : values()) {
            mappings.put(httpMethod.name(), httpMethod);
        }
    }
      
    //根据上送String字段,去map中寻找枚举值对象(HttpMethod)
    @Nullable
    public static HttpMethod resolve(@Nullable String method) {
        return (method != null ? mappings.get(method) : null);
    }
    //校验 枚举值对象(调用者)是否和map中的是一个枚举值对象。
    public boolean matches(String method) {
        return (this == resolve(method));
    }
}

使用方法

HttpMethod.GET.matches("get");

2.2 根据value获取枚举值

可以实现一个工具类,传入value值,获取到枚举对象。

/**
 * 枚举类的顶级父类
 */
public interface HasValue<T> {

    default T getValue() {
        return value();
    }

    default T value() {
        return getValue();
    }
}

自定义枚举需要实现该顶级接口:

/**
 * 货币的枚举类
 */
public enum CurrencyEnum implements HasValue<Integer> {

    CNY(1, "人民币"),
    JPY(2, "日元"),
    USD(2, "美元"),
    GBP(4, "英镑");


    private Integer code;

    private String desc;

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

    static {
        MyEnumUtils.checkDuplicate(CurrencyEnum.class);
    }

    @Override
    public Integer getValue() {
        return code;
    }

    public static void main(String[] args) {

    }
}

实现枚举的工具类:

public class MyEnumUtils {


    /**
     * 检查是否有重复元素
     */
    public static <V, T extends Enum & HasValue<V>> void checkDuplicate(Class<T> clazz) {
        List<T> values = Arrays.stream(clazz.getEnumConstants()).collect(Collectors.toList());
        Map<?, List<T>> map =
                Arrays.stream(clazz.getEnumConstants()).collect(Collectors.groupingBy(HasValue::getValue));
        for (Entry<?, List<T>> entry : map.entrySet()) {
            //com.google.common.base类
            Preconditions.checkState(entry.getValue().size() == 1,
                    "found duplicate enum: %s with unique value '%s' in %s", entry.getValue(), entry.getKey(),
                    clazz.getName());
        }
    }

    /**
     * 根据value值获取到枚举值
     */
    public static <V, T extends Enum & HasValue<V>> T fromValue(Class<T> clazz, V value) {
        return fromValue(clazz, value, null);
    }

    public static <V, T extends Enum & HasValue<V>> T fromValue(Class<T> clazz, V value, T defaultValue) {
        return Arrays.stream(clazz.getEnumConstants())
                .filter(e -> Objects.equals(e.getValue(), value))
                .findFirst().orElse(defaultValue);
    }

}

三 枚举在项目中的使用

3.1 动态的获取扩展对象

在spring项目中,有一种场景可能有一个公共接口,可能针对不同的商家/银行 有定制化的内容。一般我们采用模板方法模式,在子类中实现定制业务方法。

后续根据用户上送的业务字段动态选择不同的service进行处理

需要定制化的场景还是比较少的,故针对于大部分场景,会直接调用父类的Service。故我们需要一个缺省枚举值代表父类Service,若上送的业务类型在mapping中不存在特有的子类,那么直接返回“DEFAULT”枚举值

public enum OpenAccountEnum {
    DEFAULT("openAccount", OpenAccount.class, "通用xx接口"),
    MZ("MZService", MZService.class, "MZ-xx接口");


    //不存在多线程问题
    private static Map<String, OpenAccountEnum> mapping = new HashMap<>(16);
    //缓存服务
    private static Map<String, OpenAccount> serviceCache = new ConcurrentHashMap<>(16);

    //变量数据
    static {
        for (OpenAccountEnum openAccountEnum : values()) {
            mapping.put(openAccountEnum.name(), openAccountEnum);
        }
    }

    private String beanName;
    private Class<? extends OpenAccount> beanClass;
    private String desc;

    public String getBeanName() {
        return beanName;
    }

    public Class<? extends OpenAccount> getBeanClass() {
        return beanClass;
    }

    public String getDesc() {
        return desc;
    }

    OpenAccountEnum(String beanName, Class<? extends OpenAccount> beanClass, String desc) {
        this.beanName = beanName;
        this.beanClass = beanClass;
        this.desc = desc;
    }

    public static OpenAccountEnum resolve(String flag) {
        return (flag != null ? mapping.getOrDefault(flag.toUpperCase(), DEFAULT) : null);
    }

    public OpenAccount getBean() {
        return serviceCache.computeIfAbsent(this.name(),
                V -> SpringContextUtil.getBean(this.getBeanName(), this.getBeanClass()));
    }
}

使用方式:

    @Test
    public void test() {
        OpenAccountEnum bz = OpenAccountEnum.resolve("Bz");
        OpenAccount bzBean = bz.getBean();
        bzBean.getIndex();
    }

针对于Map操作,使用了一些JDK8的一些新特性。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353