JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比

3,667 阅读4分钟

banner窄.png

铿然架构  |  作者  /  铿然一叶 这是铿然架构的第 68 篇原创文章

相关阅读:

JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(五)函数式接口-复用,解耦之利刃
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
HikariPool源码(二)设计思想借鉴
人在职场(一)IT大厂生存法则


1. 枚举的用途

枚举可以用来定义常量,也可以当作工厂类使用,其相比常量定义,定义可以更集中;相比工厂类,表达is A(某一种类型)的语义更强。

2. 常量定义例子

2.1. 常量定义

class Constants {
    // 常量定义方式一般通过相同前缀来分类,只要保证定义在同一个代码段就没问题,如果分散到多个代码段,找起来就挺费劲
    public static final int SERV_TYPE_CAR = 1;
    public static final int SERV_TYPE_TV = 2;
    public static final int SERV_TYPE_MOBILE = 3;

    // 通过枚举定义的方式更加集中,不可能会分散到多个代码段
    public enum SERV_TYPE {
        CAR, TV, MOBILE
    }
}

2.2. 使用方式

    // 使用常量
    public static void dox(int servType) {
        switch (servType) {
            case Constants.SERV_TYPE_CAR:
                // do something
                break;
            case Constants.SERV_TYPE_MOBILE:
                // do something
                break;
            case Constants.SERV_TYPE_TV:
                // do something
                break;
            default:
                break;
        }
    }

    // 使用枚举
    public static void doy(Constants.SERV_TYPE servType) {
        switch (servType) {
            case CAR:
                // do something
                break;
            case TV:
                // do something
                break;
            case MOBILE:
                // do something
                break;
            default:  // 使用枚举作为参数时,这个分支实际可以去掉,不需要考虑非法值的异常处理
                break;
        }
    }

可见,在使用上虽然区别不大,但枚举的好处是在定义时更加集中,好维护,同时还限定了入参的取值范围,使得更不容易发生参数输入错误的情况,不需要考虑输入不合法值时的异常处理。

3. 工厂类用法例子

枚举充当工厂类的用法在很多开源项目中都可以看到,其作用和工厂类一样。先看下例子的类结构:

职责
CacheType缓存类型,枚举类,通过它来获取对应的缓存类实例
BasicCache缓存类接口,对外暴露
ConcurrentMapCacheConcurrentMap实现的缓存,可以不对外暴露
LinkedHashMapCacheLinkedHashMap实现的缓存,可以不对外暴露

3.1. 代码

3.1.1. 缓存接口

public interface BasicCache<K, V> {
    V get(K key);

    void put(K key, V value);
}

3.1.2. 缓存实现类

// 注:没有public修饰,只在包内访问,屏蔽了可见性,对外只暴露BasicCache
final class LinkedHashMapCache<K, V> implements BasicCache<K, V> {
    private final Map<K, V> map;

    public LinkedHashMapCache(int maximumSize, boolean accessOrder) {
        map = new BoundedLinkedHashMap<>(maximumSize, accessOrder);
    }

    @Override
    public V get(K key) {
        synchronized (map) {
            return map.get(key);
        }
    }

    @Override
    public void put(K key, V value) {
        synchronized (map) {
            map.put(key, value);
        }
    }

    static final class BoundedLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
        private static final long serialVersionUID = 1L;
        private final int maximumSize;

        public BoundedLinkedHashMap(int maximumSize, boolean accessOrder) {
            super(maximumSize, 0.75f, accessOrder);
            this.maximumSize = maximumSize;
        }

        @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            return size() > maximumSize;
        }
    }
}

final class ConcurrentMapCache<K, V> implements BasicCache<K, V> {
    private final ConcurrentMap<K, V> map;

    public ConcurrentMapCache(ConcurrentMap<K, V> map) {
        this.map = requireNonNull(map);
    }

    @Override
    public V get(K key) {
        return map.get(key);
    }

    @Override
    public void put(K key, V value) {
        map.put(key, value);
    }
}

3.1.3. 缓存类型

public enum CacheType {
    ConcurrentHashMap {
        @Override public <K, V> BasicCache<K, V> create(int maximumSize) {
            return new ConcurrentMapCache<>(new ConcurrentHashMap<>(maximumSize));
        }
    },
    LinkedHashMap {
        @Override public <K, V> BasicCache<K, V> create(int maximumSize) {
            return new LinkedHashMapCache<>(maximumSize, true);
        }
    };

    public abstract <K, V> BasicCache<K, V> create(int maximumSize);
}

3.1.4. 枚举类使用

// 使用枚举,可充当工厂类作用
BasicCache<String, String> enumConcurrentHash = CacheType.ConcurrentHashMap.create(2);
BasicCache<String, String> enumLinkedHash = CacheType.LinkedHashMap.create(2);

可以看到,此时枚举类充当了工厂类的作用。

3.1.5. 对比直接实例化

如果ConcurrentMapCache和LinkedHashMapCache定义为public的,那么可以直接实例化,如下:

BasicCache<String, String> concurrentMapCache = new ConcurrentMapCache<>(new ConcurrentHashMap<>(2));
BasicCache<String, String> linkedHashMapCache = new LinkedHashMapCache<>(2, true);

既然可以直接实例化,为什么还需要通过枚举来创建实例? 其主要目的和工厂类一样:屏蔽创建类实例的逻辑,外部直接使用接口就好。

3.1.6. 对比工厂类用法

// 工厂类
public final class CacheFactory {
    public static BasicCache createConcurrentMapCache(int maximumSize) {
        return new ConcurrentMapCache<>(new ConcurrentHashMap<>(maximumSize));
    }

    public static BasicCache createLinkedHashMapCache(int maximumSize) {
        return new LinkedHashMapCache<>(maximumSize, true);
    }
}

// 使用
BasicCache<String, String> concurrentHashCache = CacheFactory.createConcurrentMapCache(2);
BasicCache<String, String> linkedHashCache = CacheFactory.createLinkedHashMapCache(2);

从以上例子来看,枚举和工厂类用法并没有多大区别,那为什么还要用枚举?此时枚举有什么价值? 回答这个问题,这还是要回到枚举的最基本用途上来-限制入参的取值范围,当需要限制时就一定要用枚举。

4. 总结

  1. 在作为常量定义使用时,如果每个取值有不同的分支逻辑,适合使用枚举定义;或者想限制取值的范围(尽管此时没有不同的分支逻辑),例如话费充值只能充值50和100,也适合使用枚举。
  2. 在作为创建工厂使用时,枚举和工厂类没啥差别,只是业务语义稍有不同,枚举的is A语义更强,可以先表达is A然后再创建具体类实例,而工厂类是一步到位创建具体类实例,此时还是要看是否需要用到通过枚举类来限制入参取值的目的,如果有这个目的,那么就使用枚举。

end.


<--阅过留痕,左边点赞!