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的一些新特性。