导语:
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 ** 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枚举的主要认识。欢迎补充。我个人也会随时补充哒~