枚举类(Enum)

当我们使用关键字 ​​enum​​ 创建一个枚举时,他具有如下特性

特点

  • 构造器是私有的
  • 是一个类,默认继承​​Enum​​​,并且使用​​final​​ 修饰,可以有自己的成员变量,成员方法,静态方法、静态变量等

示例

无参数的枚举

enum  HTTP_STATUS{
OK, //是一个HTTP_STATUS枚举类型
NOT_FOUND
}

编译后的代码

enum HTTP_STATUS {
OK,
NOT_FOUND;

//默认生成私有无参构造器
private HTTP_STATUS() {
}
}

有参数的枚举

enum  HTTP_STATUS{
OK(200,"请求成功"),
NOT_FOUND(404,"无法找到资源");

private Integer code;

private String value;

private HTTP_STATUS(int code, String value) {
this.code = code;
this.value = value;
}

public Integer getCode() {
return code;
}

public String getValue() {
return value;
}
}

查看字节码

Classfile /D:/question/questions/target/classes/com/tq/questions/HTTP_STATUS.class
Last modified 2022-6-25; size 1758 bytes
MD5 checksum f38e7c76916dd6c0f40f3c055dbf8e19
Compiled from "Test.java"

//继承Enum,final修饰
final class com.tq.questions.HTTP_STATUS extends java.lang.Enum<com.tq.questions.HTTP_STATUS>
minor version: 0
major version: 52
flags: ACC_FINAL, ACC_SUPER, ACC_ENUM

...........

//每一个枚举类型都是static final 修饰的
public static final com.tq.questions.HTTP_STATUS OK;
descriptor: Lcom/tq/questions/HTTP_STATUS;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

public static final com.tq.questions.HTTP_STATUS NOT_FOUND;
descriptor: Lcom/tq/questions/HTTP_STATUS;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

public static com.tq.questions.HTTP_STATUS[] values();
descriptor: ()[Lcom/tq/questions/HTTP_STATUS;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #1 // Field $VALUES:[Lcom/tq/questions/HTTP_STATUS;
3: invokevirtual #2 // Method "[Lcom/tq/questions/HTTP_STATUS;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[Lcom/tq/questions/HTTP_STATUS;"
9: areturn
LineNumberTable:
line 20: 0

public static com.tq.questions.HTTP_STATUS valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)Lcom/tq/questions/HTTP_STATUS;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #4 // class com/tq/questions/HTTP_STATUS
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class com/tq/questions/HTTP_STATUS
9: areturn
LineNumberTable:
line 20: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 name Ljava/lang/String;

public java.lang.Integer getCode();
descriptor: ()Ljava/lang/Integer;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #8 // Field code:Ljava/lang/Integer;
4: areturn
LineNumberTable:
line 34: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/tq/questions/HTTP_STATUS;

public java.lang.String getValue();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #9 // Field value:Ljava/lang/String;
4: areturn
LineNumberTable:
line 38: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/tq/questions/HTTP_STATUS;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=7, locals=0, args_size=0
0: new #4 // class com/tq/questions/HTTP_STATUS
3: dup
4: ldc #11 // String OK
6: iconst_0
7: sipush 200
10: ldc #12 // String 请求成功
12: ldc #13 // String
14: invokespecial #14 // Method "<init>":(Ljava/lang/String;IILjava/lang/String;Ljava/lang/String;)V
17: putstatic #15 // Field OK:Lcom/tq/questions/HTTP_STATUS;
20: new #4 // class com/tq/questions/HTTP_STATUS
23: dup
24: ldc #16 // String NOT_FOUND
26: iconst_1
27: sipush 404
30: ldc #17 // String 无法找到资源
32: ldc #13 // String
34: invokespecial #14 // Method "<init>":(Ljava/lang/String;IILjava/lang/String;Ljava/lang/String;)V
37: putstatic #18 // Field NOT_FOUND:Lcom/tq/questions/HTTP_STATUS;
40: iconst_2
41: anewarray #4 // class com/tq/questions/HTTP_STATUS
44: dup
45: iconst_0
46: getstatic #15 // Field OK:Lcom/tq/questions/HTTP_STATUS;
49: aastore
50: dup
51: iconst_1
52: getstatic #18 // Field NOT_FOUND:Lcom/tq/questions/HTTP_STATUS;
55: aastore
56: putstatic #1 // Field $VALUES:[Lcom/tq/questions/HTTP_STATUS;
59: return
LineNumberTable:
line 4: 0
line 5: 20
line 3: 40

通过汇编指令,发现了枚举类型除了我们自己定义的方法外,还有如下方法

//获取所有的枚举类型
HTTP_STATUS[] values()

//根据枚举名称获取对应的枚举类型
HTTP_STATUS valueOf(java.lang.String);

此外,默认是继承了 Enum 类型,并且用 final 修饰

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {

.................

/**
* 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");
}

/**
* Throws CloneNotSupportedException. This guarantees that enums
* are never cloned, which is necessary to preserve their "singleton"
* status.
*
* @return (never returns)
*/
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}

为什么说枚举创建单例是安全的

枚举类型的成员变量是 ​​static final​​ 修饰的,在类加载阶段就已经被被赋值了,而类加载阶段是线程安全的

现在我们想一个办法创建一个对象来破环枚举单例,我们知道的有如下方法

  • Java关键字new,但是枚举类型的构造器是私有的(​​private​​)
  • 通过反射,但是反射也是依赖于构造器
  • 反序列化,因为枚举类型继承了Enum,但是Enum的​​readObject​​ 方法直接抛异常
  • 通过 clone 方法,因为枚举类型继承了Enum,但是Enum的​​clone​​​ 方法被​​final​​ 修饰 ,而且方法直接抛异常

综上,使用枚举创建单例是线程安全的