枚举最佳实践
概述
java世界数据分两种类型,基本数据类型和引用数据类型。引用数据类型分为类和接口。枚举是一种特殊的类,注解是一种特殊的接口。
基于以上可知,枚举是一种特殊的类,所谓的
enum
关键字其实是编译器语法糖。每一个枚举类编译之后反编译得到的依然是class。这个class继承了java.lang.Enum
类,顶层父类依然是Object
。只不过这个类的构造方法是私有的,只是通过类里面的静态域持有了有限个本枚举类的实例。类的具体结构入标题图所示。综上可知,枚举是一种特殊的类。这个类的结构和这个类的有限多个实例对象放在一起,形成了枚举类。有了基本的概念之后,分享如下几点。
FastJson中的序列化特性枚举
com.alibaba.fastjson.parser.Feature
package com.alibaba.fastjson.parser;
/**
* @author wenshao[szujobs@hotmail.com]
*/
/**
* 特性枚举类,
* 每个枚举的mask占用int的32个bit位的不同位域
*/
public enum Feature {
/**
* 枚举实例1
* ordinal 默认值:0
* <p>
* mask:0b0000_0001
*/
AutoCloseSource,
/**
* 枚举实例2
* ordinal 默认值:1
* mask:0b0000_0010
*/
AllowComment,
/**
* 枚举实例3
* ordinal 默认值:2
* mask:0b0000_0100
*/
AllowUnQuotedFieldNames,
/**
* 枚举实例4
* ordinal 默认值:3
* mask:0b0000_1000
*/
AllowSingleQuotes,
/**
* 枚举实例5
* ordinal 默认值:4
* mask:0b0001_0000
*/
InternFieldNames,
/**
* 枚举实例6
* ordinal 默认值:5
* mask:0b0010_0000
*/
AllowISO8601DateFormat;
Feature() {
//在构造方法中将1左移每个枚举实例的ordinal位,得到实例字段 掩码mask值
mask = (1 << ordinal());
}
public final int mask;
public final int getMask() {
return mask;
}
//枚举类的静态方法,检查一个int值是否包含某个制定特性,通过位操作,得到的结果大于0则说明int值在指定枚举的位域上有值
public static boolean isEnabled(int features, Feature feature) {
return (features & feature.mask) != 0;
}
//叠加int值,将需要包含的枚举值得mask通过位操作叠加到int值上,包含特性用|,不包含用& ~
public static int config(int features, Feature feature, boolean state) {
if (state) {
features |= feature.mask;
} else {
features &= ~feature.mask;
}
return features;
}
//将一个特性数据转换成特性int
public static int of(Feature[] features) {
if (features == null) {
return 0;
}
int value = 0;
for (Feature feature : features) {
value |= feature.mask;
}
return value;
}
}```
如上所示,FastJSON中,有一个枚举用来定义序列化过程的特性。利用枚举类中每个实例都默认生成的ordinal
值,让int 类型的1左移。利用位域的互斥,得到每个枚举实例的掩码 mask
。这样的好处是可以通过一个int值包含的1的位置来得到包含的枚举值。在传递参数时,我们只需要传递一个int值,就可以利用静态方法isEnable
来检查是否包含此特性。一个int值可以有32个位域,也就是可以定义32个枚举实例。如果还不够,用long类型,包含64个位域。一般足够啦。
JAVA并发工具包中的时间单位枚举
java.util.concurrent.TimeUnit
/** * 时间单位枚举
/**
* 时间单位枚举
* 枚举类中不丰富部分方法实现了,用作各个枚举实例的公用方法。
* 还有部分方法空实现,并抛了异常,用于被枚举实例覆盖重写。
* 实现了某种层次上的策略模式。
* 枚举其实还可以实现接口,同时也可以定义抽象方法。
* 抽闲方法被枚举实例通过匿名内部类的方式实现。
*/
public enum TimeUnit {
/**
* Time unit representing one thousandth of a microsecond
*/
//枚举实例
NANOSECONDS {
//重写覆盖枚举类中定义的空实现的方法。
public long toNanos(long d) {
return d;
}
public long toMicros(long d) {
return d / (C1 / C0);
}
public long toMillis(long d) {
return d / (C2 / C0);
}
public long toSeconds(long d) {
return d / (C3 / C0);
}
public long toMinutes(long d) {
return d / (C4 / C0);
}
public long toHours(long d) {
return d / (C5 / C0);
}
public long toDays(long d) {
return d / (C6 / C0);
}
public long convert(long d, TimeUnit u) {
return u.toNanos(d);
}
int excessNanos(long d, long m) {
return (int) (d - (m * C2));
}
};
// Handy constants for conversion methods
static final long C0 = 1L;
static final long C1 = C0 * 1000L;
static final long C2 = C1 * 1000L;
static final long C3 = C2 * 1000L;
static final long C4 = C3 * 60L;
static final long C5 = C4 * 60L;
static final long C6 = C5 * 24L;
static final long MAX = Long.MAX_VALUE;
static long x(long d, long m, long over) {
if (d > over) return Long.MAX_VALUE;
if (d < -over) return Long.MIN_VALUE;
return d * m;
}
public long convert(long sourceDuration, TimeUnit sourceUnit) {
throw new AbstractMethodError();
}
public long toNanos(long duration) {
throw new AbstractMethodError();
}
public long toMicros(long duration) {
throw new AbstractMethodError();
}
public long toMillis(long duration) {
throw new AbstractMethodError();
}
public long toSeconds(long duration) {
throw new AbstractMethodError();
}
public long toMinutes(long duration) {
throw new AbstractMethodError();
}
public long toHours(long duration) {
throw new AbstractMethodError();
}
public long toDays(long duration) {
throw new AbstractMethodError();
}
abstract int excessNanos(long d, long m);
public void timedWait(Object obj, long timeout)
throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
obj.wait(ms, ns);
}
}
public void timedJoin(Thread thread, long timeout)
throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
thread.join(ms, ns);
}
}
public void sleep(long timeout) throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
Thread.sleep(ms, ns);
}
}
}
该枚举中,展示了枚举实例和枚举类的关系。枚举类其实就是一个普通的类,也就是说可以被匿名实例化。通过匿名实例化,可以重写覆盖枚举的方法。实现某种程度上的策略模式。
枚举可以实现接口
public enum ConfigurationPlatform implements keyValueLoaderProvider {
DATABASE() {
@Override
public DynamicValueLoader keyValueLoader() {
return null;
}
},
ZOOKEEPER() {
@Override
public DynamicValueLoader keyValueLoader() {
return null;
}
};
}
枚举实现接口后,实现了枚举的拓展性,通过多态,同一类型的枚举不一定需要放在统一个枚举类里,只要实现了同一个接口,他们就具有同样的功能。参数化类型为<? extends 接口 & Enum>
。即如下:
该枚举实现了Function接口
通过向上转型得到集合
枚举和反射
枚举和反射
java中所有的类都被抽象成了Class对象,因此Class类对象囊括了枚举类这个特性,也提供了枚举类相关的方法。
//判断一个Class对象是否是枚举类
public boolean isEnum() {
// An enum must both directly extend java.lang.Enum and have
// the ENUM bit set; classes for specialized enum constants
// don't do the former.
return (this.getModifiers() & ENUM) != 0 &&
this.getSuperclass() == java.lang.Enum.class;
}
//得到一个枚举类的所有枚举实例
public T[] getEnumConstants() {
T[] values = getEnumConstantsShared();
return (values != null) ? values.clone() : null;
}
如果配合接口,就可以实现这样的业务场景:
我们总是在页面上会有下拉选择框,下拉选择框是的值是离散的。这种场景适合用枚举。我们为每个下拉选择框定义一个枚举,枚举实现一个接口,定义code()和name()方法。然后通过反射得到这个枚举的所有实例,转型调用name和code方法返回给前端动态生成下拉列表。这种实现的好处在于,前端传递的每一个值,在后端都能匹配一个枚举,同时如果枚举实例增加了,前端生成的下拉列表选项也能动态增加。代码如下:
用于标记select枚举的注解
被标记注解标记的枚举。定义了name和code字段
扫描的到字节码对象,反射调用方法,生成SELECT_ITEMS。用于返回给前端构造动态的select下拉列表
总结
以上总结了我对于java枚举的主要认识。欢迎补充。我个人也会随时补充哒~