java枚举
1 - 为什么会有枚举?
下面使用单例模式
/**
* 季节
*/
public class Season {
private Integer value;
private String name;
// 春天
public static final Season SPRING = new Season(1,"春天");
// 夏天
public static final Season SUMMER = new Season(2,"夏天");
// 秋天
public static final Season AUTUMN = new Season(3,"秋天");
// 冬天
public static final Season WINTER = new Season(4,"冬天");
private Season() {
}
private Season(Integer value, String name) {
this.value = value;
this.name = name;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
下面是主要代码
// 春天
public static final Season SPRING = new Season();
// 夏天
public static final Season SUMMER = new Season();
// 秋天
public static final Season AUTUMN = new Season();
// 冬天
public static final Season WINTER = new Season();
感觉太麻烦了,太多重复代码,主要变的也就是变量名而已,所以就可以使用枚举来简化
/**
* 季节枚举类
*/
public enum EnumSeason {
// 每一个都是枚举项
SPRING,SUMMER,AUTUMN,WINTER;
}
2 - 枚举本质
/**
* 季节枚举类
*/
public enum SeasonEnum {
// 每一个都是枚举项
SPRING,SUMMER,AUTUMN,WINTER;
public static void main(String[] arge) {
}
}
以下使用javap -v SeasonEnum.class反编译得来
C:\Users\HP\Desktop\Project\out\production\Project\com\temp>javap -v SeasonEnum.class
Classfile /C:/Users/HP/Desktop/Project/out/production/Project/com/temp/SeasonEnum.class
Last modified 2023-9-18; size 1146 bytes
MD5 checksum 9a56bc2cb6b71fb9359c0d39a5cf652f
Compiled from "SeasonEnum.java"
public final class com.temp.SeasonEnum extends java.lang.Enum<com.temp.SeasonEnum>
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
Constant pool:
#1 = Fieldref #4.#46 // com/temp/SeasonEnum.$VALUES:[Lcom/temp/SeasonEnum;
#2 = Methodref #47.#48 // "[Lcom/temp/SeasonEnum;".clone:()Ljava/lang/Object;
#3 = Class #23 // "[Lcom/temp/SeasonEnum;"
#4 = Class #49 // com/temp/SeasonEnum
#5 = Methodref #16.#50 // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#6 = Methodref #16.#51 // java/lang/Enum."<init>":(Ljava/lang/String;I)V
#7 = String #17 // SPRING
#8 = Methodref #4.#51 // com/temp/SeasonEnum."<init>":(Ljava/lang/String;I)V
#9 = Fieldref #4.#52 // com/temp/SeasonEnum.SPRING:Lcom/temp/SeasonEnum;
#10 = String #19 // SUMMER
#11 = Fieldref #4.#53 // com/temp/SeasonEnum.SUMMER:Lcom/temp/SeasonEnum;
#12 = String #20 // AUTUMN
#13 = Fieldref #4.#54 // com/temp/SeasonEnum.AUTUMN:Lcom/temp/SeasonEnum;
#14 = String #21 // WINTER
#15 = Fieldref #4.#55 // com/temp/SeasonEnum.WINTER:Lcom/temp/SeasonEnum;
#16 = Class #56 // java/lang/Enum
#17 = Utf8 SPRING
#18 = Utf8 Lcom/temp/SeasonEnum;
#19 = Utf8 SUMMER
#20 = Utf8 AUTUMN
#21 = Utf8 WINTER
#22 = Utf8 $VALUES
#23 = Utf8 [Lcom/temp/SeasonEnum;
#24 = Utf8 values
#25 = Utf8 ()[Lcom/temp/SeasonEnum;
#26 = Utf8 Code
#27 = Utf8 LineNumberTable
#28 = Utf8 valueOf
#29 = Utf8 (Ljava/lang/String;)Lcom/temp/SeasonEnum;
#30 = Utf8 LocalVariableTable
#31 = Utf8 name
#32 = Utf8 Ljava/lang/String;
#33 = Utf8 <init>
#34 = Utf8 (Ljava/lang/String;I)V
#35 = Utf8 this
#36 = Utf8 Signature
#37 = Utf8 ()V
#38 = Utf8 main
#39 = Utf8 ([Ljava/lang/String;)V
#40 = Utf8 arge
#41 = Utf8 [Ljava/lang/String;
#42 = Utf8 <clinit>
#43 = Utf8 Ljava/lang/Enum<Lcom/temp/SeasonEnum;>;
#44 = Utf8 SourceFile
#45 = Utf8 SeasonEnum.java
#46 = NameAndType #22:#23 // $VALUES:[Lcom/temp/SeasonEnum;
#47 = Class #23 // "[Lcom/temp/SeasonEnum;"
#48 = NameAndType #57:#58 // clone:()Ljava/lang/Object;
#49 = Utf8 com/temp/SeasonEnum
#50 = NameAndType #28:#59 // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#51 = NameAndType #33:#34 // "<init>":(Ljava/lang/String;I)V
#52 = NameAndType #17:#18 // SPRING:Lcom/temp/SeasonEnum;
#53 = NameAndType #19:#18 // SUMMER:Lcom/temp/SeasonEnum;
#54 = NameAndType #20:#18 // AUTUMN:Lcom/temp/SeasonEnum;
#55 = NameAndType #21:#18 // WINTER:Lcom/temp/SeasonEnum;
#56 = Utf8 java/lang/Enum
#57 = Utf8 clone
#58 = Utf8 ()Ljava/lang/Object;
#59 = Utf8 (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
{
public static final com.temp.SeasonEnum SPRING;
descriptor: Lcom/temp/SeasonEnum;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final com.temp.SeasonEnum SUMMER;
descriptor: Lcom/temp/SeasonEnum;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final com.temp.SeasonEnum AUTUMN;
descriptor: Lcom/temp/SeasonEnum;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final com.temp.SeasonEnum WINTER;
descriptor: Lcom/temp/SeasonEnum;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static com.temp.SeasonEnum[] values();
descriptor: ()[Lcom/temp/SeasonEnum;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #1 // Field $VALUES:[Lcom/temp/SeasonEnum;
3: invokevirtual #2 // Method "[Lcom/temp/SeasonEnum;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[Lcom/temp/SeasonEnum;"
9: areturn
LineNumberTable:
line 3: 0
public static com.temp.SeasonEnum valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)Lcom/temp/SeasonEnum;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #4 // class com/temp/SeasonEnum
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/temp/SeasonEnum
9: areturn
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 name Ljava/lang/String;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 arge [Ljava/lang/String;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
0: new #4 // class com/temp/SeasonEnum
3: dup
4: ldc #7 // String SPRING
6: iconst_0
7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #9 // Field SPRING:Lcom/temp/SeasonEnum;
13: new #4 // class com/temp/SeasonEnum
16: dup
17: ldc #10 // String SUMMER
19: iconst_1
20: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
23: putstatic #11 // Field SUMMER:Lcom/temp/SeasonEnum;
26: new #4 // class com/temp/SeasonEnum
29: dup
30: ldc #12 // String AUTUMN
32: iconst_2
33: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
36: putstatic #13 // Field AUTUMN:Lcom/temp/SeasonEnum;
39: new #4 // class com/temp/SeasonEnum
42: dup
43: ldc #14 // String WINTER
45: iconst_3
46: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
49: putstatic #15 // Field WINTER:Lcom/temp/SeasonEnum;
52: iconst_4
53: anewarray #4 // class com/temp/SeasonEnum
56: dup
57: iconst_0
58: getstatic #9 // Field SPRING:Lcom/temp/SeasonEnum;
61: aastore
62: dup
63: iconst_1
64: getstatic #11 // Field SUMMER:Lcom/temp/SeasonEnum;
67: aastore
68: dup
69: iconst_2
70: getstatic #13 // Field AUTUMN:Lcom/temp/SeasonEnum;
73: aastore
74: dup
75: iconst_3
76: getstatic #15 // Field WINTER:Lcom/temp/SeasonEnum;
79: aastore
80: putstatic #1 // Field $VALUES:[Lcom/temp/SeasonEnum;
83: return
LineNumberTable:
line 4: 0
line 3: 52
}
Signature: #43 // Ljava/lang/Enum<Lcom/temp/SeasonEnum;>;
SourceFile: "SeasonEnum.java"
2.1 - 分析
分析之前给大家展示一下Enum抽象类的一段源码👇,等一下用得着
// Enum抽象类一段源码
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
// 用于保存常量名
private final String name;
// 返回常量名
public final String name() {
return name;
}
// 用于排序
private final int ordinal;
// 返回用于排序的常量
public final int ordinal() {
return ordinal;
}
// *(注意,Enum子类会调用这个构造方法)Enum构造方法,给上面两个常量初始化
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
}
第6行:public final class com.temp.SeasonEnum extends java.lang.Enum<com.temp.SeasonEnum>
枚举类型本质是一个被final修饰的类(不可被继承的类),并且继承Enum抽象类,泛型是SeasonEnum
第71、75、79、83行:
public static final com.temp.SeasonEnum SPRING; // 春天
public static final com.temp.SeasonEnum SUMMER; // 夏天
public static final com.temp.SeasonEnum AUTUMN; // 秋天
public static final com.temp.SeasonEnum WINTER; // 冬天
那四个代码分别创建,静态的SeasonEnum类型的常量
第87、99行:
public static com.temp.SeasonEnum[] values();
public static com.temp.SeasonEnum valueOf(java.lang.String);
values和valueOf是来自Enum抽象类
第127行及以下: static {};
静态代码块,用来初始化。
以下都是static静态代码块中的代码
第132行:根据注释可以看出,创建了一个SeasonEnum类
第134行: 4: ldc #7 // String SPRING
ldc指令:将位于#7的数据压入栈中
#7:存的是值的数据类型String,但值确位于#17中。
#17:值为SPRING,UTF-8字符编码
第135行:iconst_0
iconst指令:将0压入栈中
第136行: 7: invokespecial #8 // Method “<init>”:(Ljava/lang/String;I)V
invokespecial指令:调用#8方法
Method “<init>”:(Ljava/lang/String;I)V解读:
Method含义:方法;
<init>含义:初始化(构造方法)
L含义:引用数据类型;
java/lang/String含义:参数的数据类型;
I含义:Integer类型,表示参数的数据类型;
V含义:方法无返回值
注释:通过#8中的注释可以得知,调用的是SeasonEnum这个类的构造方法,并且把上面压入栈中的两个数据传入方法中
第137行:10: putstatic #9 // Field SPRING:Lcom/temp/SeasonEnum;
putstatic指令:用于给指定类的静态域赋值的。
注释:通过注释可以得知,把刚刚创建的SeasonEnum对象赋值给SPRING静态常量
第132行——154行都是给那四个静态常量创建对象赋值的。
疑问的是SeasonEnum类只有一个空参构造,为什么会有两个参数传入呢,并且运行成功了?
解答:我们都知道,创建一个对象的时候,会调用父类的构造方法创建父类。
上面已经得知,SeasonEnum是继承Enum抽象类的。而且Enum类就只有一个有参构造方法,并没有无参构造。
结论:那两个值是用于创建SeasonEnum父类对象构造方法所用到的参数。
验证结论
public enum SeasonEnum {
// 每一个都是枚举项
SPRING,SUMMER,AUTUMN,WINTER;
public static void main(String[] args) {
// 获取Enum构造方法String类型的参数
String name = SPRING.name();
// 获取Enum构造方法int类型的参数
int ordinal = SPRING.ordinal();
// 输出name、ordinal
System.out.println(name);
System.out.println(ordinal);
}
}
结果
如果有兴趣可以反编译一下Season类(不是枚举类)的字节码文件
其实跟枚举类反编译过来的差不多一模一样。
3 - 常用方法
3.1 - values方法
这是一个静态方法,属于我们自己写的这个枚举类,不属于Enum这个类。
功能:用于返回这个枚举类中所有的枚举项,返回值是枚举类型的数组。
疑问:奇怪的是我们没有写这个方法,Enum这个父类也没有values这个方法,为什么会有这个方法?这个方法到底是从哪里来的呢?
解答:这个是编译器自动生成的。通过上面的反编译后得到的信息,第87行就生成了这个静态方法。
public static com.temp.SeasonEnum[] values();
3.2 - valueOf方法
这是一个静态方法,属于我们自己写的这个枚举类,不属于Enum这个类。
功能:通过传入枚举项的name(String类型),获取这个枚举项的对象。
疑问:在Enum源代码中我们也看到了valueOf这个方法,这里说的是这个方法吗?
解答:并不是,这里说的是编译器自动生成的valueOf,跟上面的values方法一样。(反编译:第99行)
Enum和自动生成的valueOf是重载关系。
public static com.temp.SeasonEnum valueOf(java.lang.String);
3.3 - name方法
用于返回枚举项中父类的name值。来自Enum父类的方法。
//以下是都来自Enum类源码
// 属性值
private final String name;
// name方法的源代码
public final String name() {
return name;
}
3.4 - ordinal方法
用于返回枚举项中父类的ordinal值。来自Enum父类的方法。
//以下是都来自Enum类源码
// 属性值
private final int ordinal;
// ordinal方法的源代码
public final int ordinal() {
return ordinal;
}
4 - 枚举类可以有自己的方法和变量
每个枚举类(注意:不是枚举项)都可以有自己的方法和变量
/**
* 季节枚举类
*/
public enum SeasonEnum {
/**
* 调用有参构造
*/
SPRING("春天","又到了万物复苏的季节!"),
SUMMER("夏天","又到了女孩子穿裙子的季节!"),
AUTUMN("秋天","又到了丰收的季节!"),
WINTER; // 调用无参构造
private String name;
private String detail;
// 无参构造方法
SeasonEnum() {
}
// 有参构造方法
SeasonEnum(String name, String detail) {
this.name = name;
this.detail = detail;
}
public static void main(String[] args) {
SeasonEnum[] values = SeasonEnum.values();
for (SeasonEnum value : values) {
System.out.println(value.detail);
}
}
}
结果
可以通过反编译可以看到第7行调用的构造方法和第13行调用的构造方法不一样,一个是有参、一个是无参。
34: new #4 // class com/temp/SeasonEnum
37: dup
38: ldc #21 // String AUTUMN
40: iconst_2
41: ldc #22 // String 秋天
43: ldc #23 // String 又到了丰收的季节!
45: invokespecial #15 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)V
48: putstatic #24 // Field AUTUMN:Lcom/temp/SeasonEnum;
51: new #4 // class com/temp/SeasonEnum
54: dup
55: ldc #25 // String WINTER
57: iconst_3
58: invokespecial #26 // Method "<init>":(Ljava/lang/String;I)V
61: putstatic #27 // Field WINTER:Lcom/temp/SeasonEnum;
5 - switch语句中的ENUM
// 季节枚举类
public enum SeasonEnum {
// 枚举项
SPRING,SUMMER,AUTUMN,WINTER;
public static void main(String[] args) {
SeasonEnum se = SPRING;
switch(se) {
case SPRING:
System.out.println("春天");
break;
case SUMMER:
System.out.println("夏天");
break;
case AUTUMN:
System.out.println("秋天");
break;
case WINTER:
System.out.println("冬天");
break;
}
}
}
6 - 静态导入
作用:使用静态导入在类中要使用其他类的静态属性、静态方法时,可以直接忽略类名,直接使用成员。可以方便地使用某个类的静态成员,提高代码的可读性和编写效率。
包括枚举项,经过上面的学习,我们可以知道枚举项的本质就是一个静态常量,所以一样适用。
import static com.temp.SeasonEnum.*;
6.1 - 对比
静态导入👇
import static com.temp.SeasonEnum.*;
public class Temp {
public static void main(String[] args) {
// 因为静态导入,所以可以直接使用SPRING枚举项。
System.out.println(SPRING);
}
}
普通导入👇
public class Temp {
public static void main(String[] args) {
System.out.println(SeasonEnum.SPRING);
}
}
7 - 懒汉式单例模式
public class Singleton {
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.INSTANCE.singleton;
}
private enum SingletonHolder{
INSTANCE;
private Singleton singleton;
private SingletonHolder(){
singleton = new Singleton();
}
}
}
INSTANCE(枚举项)静态常量 = new SingletonHolder(); //(枚举类)
SingletonHolder枚举类中有一个Singleton类型的变量。