枚举最佳实践
概述
java世界数据分两种类型,基本数据类型和引用数据类型。引用数据类型分为类和接口。枚举是一种特殊的类,注解是一种特殊的接口。
基于以上可知,枚举是一种特殊的类,所谓的
enum关键字其实是编译器语法糖。每一个枚举类编译之后反编译得到的依然是class。这个class继承了java.lang.Enum类,顶层父类依然是Object。只不过这个类的构造方法是私有的,只是通过类里面的静态域持有了有限个本枚举类的实例。类的具体结构入标题图所示。综上可知,枚举是一种特殊的类。这个类的结构和这个类的有限多个实例对象放在一起,形成了枚举类。有了基本的概念之后,分享如下几点。
FastJson中的序列化特性枚举
com.alibaba.fastjson.parser.Feature
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package com.alibaba.fastjson.parser;
/**
* @author wenshao[[email protected]]
*/
/**
* 特性枚举类,
* 每个枚举的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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
/** * 时间单位枚举
/**
* 时间单位枚举
* 枚举类中不丰富部分方法实现了,用作各个枚举实例的公用方法。
* 还有部分方法空实现,并抛了异常,用于被枚举实例覆盖重写。
* 实现了某种层次上的策略模式。
* 枚举其实还可以实现接口,同时也可以定义抽象方法。
* 抽闲方法被枚举实例通过匿名内部类的方式实现。
*/
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);
}
}
}
该枚举中,展示了枚举实例和枚举类的关系。枚举类其实就是一个普通的类,也就是说可以被匿名实例化。通过匿名实例化,可以重写覆盖枚举的方法。实现某种程度上的策略模式。
枚举可以实现接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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类对象囊括了枚举类这个特性,也提供了枚举类相关的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
//判断一个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枚举的主要认识。欢迎补充。我个人也会随时补充哒~






