简介
- 枚举:Java5开始
- 什么是枚举:枚举是一个被命名的整型常数的集合,用于声明一组带标识符的常数。枚举在曰常生活中很常见,例如一个人的性别只能是“男”或者“女”,一周的星期只能是 7 天中的一个等。类似这种当一个变量有几种固定可能的取值时,就可以将它定义为枚举类型
- 任意两个枚举成员不能具有相同的名称,且它的常数值必须在该枚举的基础类型的范围之内,多个枚举成员之间使用逗号分隔
- 枚举的数据是有限的
-
继承体系
每个枚举类都继承自 java.lang.Enum ```java public abstract class Enum
> implements Comparable , Serializable { // 枚举的名称 private final String name; // 获取名称 public final String name() {
return name;
} // 顺序 从0开始 private final int ordinal; public final int ordinal() {
return ordinal;}
protected Enum(String name, int ordinal) {
this.name = name; this.ordinal = ordinal;}
public String toString() {
return name;}
public final boolean equals(Object other) {
return this==other;} public final int hashCode() {
return super.hashCode();}
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();}
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o; Enum<E> self = this; if (self.getClass() != other.getClass() && // optimization self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException(); return self.ordinal - other.ordinal;}
public final Class
getDeclaringClass() { Class<?> clazz = getClass(); Class<?> zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;}
public static
> T valueOf(Class enumType, String name) { T result = enumType.enumConstantDirectory().get(name); if (result != null) return result; if (name == null) throw new NullPointerException("Name is null"); throw new IllegalArgumentException( "No enum constant " + enumType.getCanonicalName() + "." + name);}
protected final void finalize() { }
/**
prevent default deserialization */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException(“can’t deserialize enum”); }
private void readObjectNoData() throws ObjectStreamException { throw new InvalidObjectException(“can’t deserialize enum”); } }
<a name="t9Jso"></a>
# 常用方法
| 方法名称 | 描述 |
| --- | --- |
| values() | 以数组形式返回枚举类型的所有成员 |
| valueOf() | 将普通字符串转换为枚举实例 |
| compareTo() | 比较两个枚举成员在定义时的顺序 |
| ordinal() | 获取枚举成员的索引位置 |
<a name="am3FN"></a>
# 枚举的定义和使用
<a name="u049J"></a>
## 案例:性别
```java
public enum GenderTypeEnum {
MALE, FEMALE;
}
案例:布尔值
// 场景 - 数据库的删除状态 0 未删除 1 已经删除
public enum BooleanIntEnum {
/**
* 已删除
*/
Y(1, "Y"),
/**
* 未删除
*/
N(0, "N");
private final int code;
private final String desc;
BooleanIntEnum(int code, String desc) {
this.code = code;
this.desc = desc;
}
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
案例:带非静态方法
public enum OrderTypeEnum {
SUCCESS, FAIL;
// 是否成功
public boolean isSuccess() {
return this == SUCCESS;
}
// 是否失败
public boolean isFail() {
return this == FAIL;
}
}
案例:带静态方法
// 以下案例的好处,查询时间从 O(n) => O(logN)
public enum Const {
AA, AB, AC, AD, AE, AF, AG, AH, AI, AJ, AK, AL, AM, //
AN, AO, AP, AQ, AR, AS, AT, AU, AV, AW, AX, AY, AZ, BA, BB, BC, BD, //
BE, BF, BG, BH, BI, BJ, BK, BL, BM, BN, BO, BP, BQ, BR, BS, BT, BU, //
BV, BW, BX, BY, BZ, CA, CB, CC, CD, CE, CF, CG, CH, CI, CJ, CK, CL;//
private static final Map<Integer, Const> MAPPER = new HashMap<>();
static {
Const[] values = Const.values();
for (Const value : values) {
MAPPER.put(value.ordinal(), value);
}
}
/**
* 根据ordinal获取枚举
*
* @param ordinal ordinal
* @return 返回获取的枚举
*/
public static Const getByOrdinal(int ordinal) {
return MAPPER.get(ordinal);
}
}
案例:带有抽象方法
// 带有抽象方法,会有一定的作用,在后面讲到设计模式的时候
// 我们会讲解 状态机 在基于枚举的实现
// OrderType 中定义抽象方法,那么子类都要实现,这样可以定制枚举
public enum OrderType {
GRAB {
@Override
protected void doXxx() {
System.out.println("OrderType.doXxx");
}
},
NORMAL {
@Override
protected void doXxx() {
System.out.println("OrderType.doXxx");
}
};
protected abstract void doXxx();
}
使用枚举实现单例
什么是单例
- 保证其在运行中只有一个实例,为了节省资源和安全
- 单例类只能有一个实例
- 单例类必须自己创建自己的唯一实例
-
代码实现
```java public enum EnumSingle { INSTANCE;
private TargetClass instance;
EnumSingle() {
this.instance = new TargetClass();}
public final TargetClass getInstance() {
return instance;}
}
class TargetClass {
}
```java
public class EnumSingleMain {
public static void main(String[] args) throws Exception {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
TargetClass instance = EnumSingle.INSTANCE.getInstance();
System.out.println(instance);
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
TargetClass instance = EnumSingle.INSTANCE.getInstance();
System.out.println(instance);
}
}
}).start();
}
}
枚举单例为什么是安全的
- 当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的
- 枚举的特性
- 枚举可以实现接口,但不能继承接口,也不能被继承
- 枚举类是final的,所以不能继承
- 枚举类的构造方法是私有的
- 枚举成员是静态、final和public
- 枚举成员是枚举类的实例
- 枚举本质上是通过普通的类来实现的,只是编译器为我们进行了处理。每个枚举类型都继承自java.lang.Enum,并自动添加了values和valueOf方法。而每个枚举常量是一个静态常量字段,使用内部类实现,该内部类继承了枚举类。所有枚举常量都通过静态代码块来进行初始化,即在类加载期间就初始化。另外通过把clone、readObject、writeObject这三个方法定义为final的,同时实现是抛出相应的异常。这样保证了每个枚举类型及枚举常量都是不可变的。可以利用枚举的这两个特性来实现线程安全的单例
反编译操作
icanci@localhost ienum % javap -p EnumSingle.class Compiled from "EnumSingle.java" // final class public final class com.ldl.baselearn.ienum.EnumSingle extends java.lang.Enum<com.ldl.baselearn.ienum.EnumSingle> { // final INSTANCE public static final com.ldl.baselearn.ienum.EnumSingle INSTANCE; private com.ldl.baselearn.ienum.TargetClass instance; // final $VALUES private static final com.ldl.baselearn.ienum.EnumSingle[] $VALUES; public static com.ldl.baselearn.ienum.EnumSingle[] values(); public static com.ldl.baselearn.ienum.EnumSingle valueOf(java.lang.String); // 私有化构造函数 private com.ldl.baselearn.ienum.EnumSingle(); public final com.ldl.baselearn.ienum.TargetClass getInstance(); // 静态代码块 static {}; }序列化操作枚举
public class EnumSingleMain { public static void main(String[] args) throws Exception { EnumSingle instance = EnumSingle.INSTANCE; SerializationUtil.writeObject(instance); EnumSingle single = (EnumSingle) SerializationUtil.readObject(); System.out.println(single); } }```java public class SerializationUtil { private static final String PATH = “/Users/icanci/ideaProjects/NewLearn/base-learn/src/main/java/com/ldl/baselearn/ienum/serTest.txt”;
/**
- 序列化 *
- @param obj obj
@throws IOException */ public static void writeObject(Object obj) throws IOException { //生成一个文件对象,文件不存在将自动创建文件 File f = new File(PATH); //构造一个对象输出流oos ObjectOutputStream oos = null; //构造一个文件输出流 FileOutputStream fileOutputStream = new FileOutputStream(f); //构造对象输出流 oos = new ObjectOutputStream(fileOutputStream); //序列化一个对象到文件变成二进制内容 oos.writeObject(obj); // 关闭资源 oos.close(); fileOutputStream.close(); }
/**
- 读取序列化的对象
- @return 返回序列化的对象
- @throws IOException */ public static Object readObject() throws IOException, ClassNotFoundException { //生成一个文件对象 File f = new File(PATH); //构建对象输入流对象 ObjectInputStream oos = null; //构建文件输入流对象 FileInputStream fileOutputStream = new FileInputStream(f); oos = new ObjectInputStream(fileOutputStream); Object obj = oos.readObject(); // 关闭资源 oos.close(); fileOutputStream.close(); return obj; } }
- 序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。
- 同时,编译器是不允许任何对这种序列化机制的定制的,因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。
- 所以有枚举序列化的场景,要注意删除枚举的情况
<a name="eJWQY"></a>
## 反射创建枚举对象
```java
public class EnumSingleMain {
public static void main(String[] args) throws Exception {
Class<EnumSingle> aClass = EnumSingle.class;
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
System.out.println(declaredConstructors);
Constructor<?> constructor = declaredConstructors[0];
constructor.setAccessible(true);
Object o = constructor.newInstance();
}
}
上述代码会抛出异常
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Constructor.java:417) at com.ldl.baselearn.ienum.EnumSingleMain.test02(EnumSingleMain.java:20) at com.ldl.baselearn.ienum.EnumSingleMain.main(EnumSingleMain.java:11) [ERROR] Command execution failed.抛出异常的原因如下
@CallerSensitive public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } } // 如果是ENUM类型,抛出异常 if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }-
枚举的数量控制在64个之内
为什么?来看下底层的实现 ```java public class Proposal { public static void main(String[] args) {
EnumSet<Const1> consts = EnumSet.allOf(Const1.class); EnumSet<LargeConst1> largeConsts = EnumSet.allOf(LargeConst1.class); System.out.println(consts.size()); System.out.println(largeConsts.size()); System.out.println(consts.getClass()); System.out.println(largeConsts.getClass());} }
enum Const1 { // 64个 AA, AB, AC, AD, AE, AF, AG, AH, AI, AJ, AK, AL, AM, AN, AO, AP, AQ, AR, AS, AT, AU, AV, AW, AX, AY, AZ, BA, BB, BC, BD, BE, BF, BG, BH, BI, BJ, BK, BL, BM, BN, BO, BP, BQ, BR, BS, BT, BU, BV, BW, BX, BY, BZ, CA, CB, CC, CD, CE, CF, CG, CH, CI, CJ, CK, CL }
enum LargeConst1 { // 多了一个ZA // 65个 AA, AB, AC, AD, AE, AF, AG, AH, AI, AJ, AK, AL, AM, AN, AO, AP, AQ, AR, AS, AT, AU, AV, AW, AX, AY, AZ, BA, BB, BC, BD, BE, BF, BG, BH, BI, BJ, BK, BL, BM, BN, BO, BP, BQ, BR, BS, BT, BU, BV, BW, BX, BY, BZ, CA, CB, CC, CD, CE, CF, CG, CH, CI, CJ, CK, CL, ZA }
// 执行结果 // 64 // 65 // class java.util.RegularEnumSet // class java.util.JumboEnumSet
- 枚举的 ordinal 是从0开始的
- 小于等于64的时候 其把所有的标号【从0开始的】存储在long的每一位上
- java将一个不多于64个枚举的枚举映射映射到了一个long类型的变量上,其他的size方法。都是通过 elements算出来的
- 一个long类型的数字包含了所有的枚举项
- 多余64个的时候 如下存储:private long elements[];
- 参见下面EnumSet源码
<a name="aXwXY"></a>
# EnumSet
```java
// 上述实例的allOf方法
public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {
EnumSet<E> result = noneOf(elementType);
result.addAll();
return result;
}
// noneOf 获取存储类型
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
Enum<?>[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");
// 小于 64 => RegularEnumSet
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
// 大于 64 => JumboEnumSet
return new JumboEnumSet<>(elementType, universe);
}
-
EnumMap
-
参考文章
- Java枚举:https://www.cnblogs.com/summerday152/p/12347079.html
