枚举 enum
在某些情况下,一个类的对象的实例有限且固定的,如季节类,它只有春夏秋冬4个对象,再比如星期,在这种场景下我们可以使用枚举。当然我们也可以有自己的方法来实现。
方案一:静态常量
public class SeasonConstant {
public static final int SPRING = 1;
public static final int SUMMER = 2;
public static final int AUTUMN = 3;
public static final int WINTER = 4;
}
这种方式,我们可以简单的表示春夏秋冬四个季节,但是扩展性很差,我们想给春夏秋冬附加更多信息的时候就无能为力的,静态常量能保证内存独此一份,更够很好的表示春夏秋冬四个季节,同时不允许别人修改。
方案二:利用类似单例模式的方案
既然使用基础数据类型无法表示丰富的内容,我们不妨把基础类型改为引用数据类型。
public class Season {
private int 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(int value, String name) {
this.value = value;
this.name = name;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
我们这样做不仅仅能够保证内存中只有四个对象,我们不妨验证一下:
public static void main(String[] args) {
System.out.println(Season.SPRING == Season.SPRING);
}
使用==比较两个对象比较的是内存地址,内存地址一样不正好说明是同一个对象嘛。
只是这种写法略显复杂,我们可以使用简单的方式表达:
首先我们先简化一下代码,去掉其他属性:
public class Season {
// 定义四个静态常量让每个季节在内存中独此一份
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 class Season {
// 定义四个静态常量让每个季节在内存中独此一份
SPRING,SUMMER,AUTUMN,WINTER;
}
如果能写成这个样子,是不是就好了,这仅仅是将所有的重复代码删掉了而已。
Java1.5 引入了 enum 来定义枚举类,就可以使用这样的书写方式了,但是要将class换成enum,如下:
public enum SeasonEnum {
SPRING, SUMMER, AUTUMN, WINTER;
}
当然,以上的例子都是为了更好的解释枚举类,事实上枚举还有
1、基本Enum特性
- 枚举类的定义
public enum SeasonEnum {
SPRING,SUMMER,AUTUMN,WINTER;
}
不妨看看字节码文件:
这个是静态常量的:
C:\Users\zn\IdeaProjects\untitled\out\production\untitled>javap -v Season.class
Classfile /C:/Users/zn/IdeaProjects/untitled/out/production/com/ydlclass/Season.class
Last modified 2021-8-28; size 1103 bytes
MD5 checksum e7dac070287209c9e80194b38fa4b27b
Compiled from "Season.java"
public class Season
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #14.#42 // java/lang/Object."<init>":()V
#2 = Fieldref #4.#43 // Season.value:I
#3 = Fieldref #4.#44 // Season.name:Ljava/lang/String;
#4 = Class #45 // Season
#5 = String #46 // 春天
#6 = Methodref #4.#47 // Season."<init>":(ILjava/lang/String;)V
#7 = Fieldref #4.#48 // Season.SPRING:LSeason;
#8 = String #49 // 夏天
#9 = Fieldref #4.#50 // Season.SUMMER:LSeason;
#10 = String #51 // 秋天
#11 = Fieldref #4.#52 // Season.AUTUMN:LSeason;
#12 = String #53 // 冬天
#13 = Fieldref #4.#54 // Season.WINTER:LSeason;
#14 = Class #55 // java/lang/Object
#15 = Utf8 value
#16 = Utf8 I
#17 = Utf8 name
#18 = Utf8 Ljava/lang/String;
#19 = Utf8 SPRING
#20 = Utf8 LSeason;
#21 = Utf8 SUMMER
#22 = Utf8 AUTUMN
#23 = Utf8 WINTER
#24 = Utf8 <init>
#25 = Utf8 ()V
#26 = Utf8 Code
#27 = Utf8 LineNumberTable
#28 = Utf8 LocalVariableTable
#29 = Utf8 this
#30 = Utf8 (ILjava/lang/String;)V
#31 = Utf8 getValue
#32 = Utf8 ()I
#33 = Utf8 setValue
#34 = Utf8 (I)V
#35 = Utf8 getName
#36 = Utf8 ()Ljava/lang/String;
#37 = Utf8 setName
#38 = Utf8 (Ljava/lang/String;)V
#39 = Utf8 <clinit>
#40 = Utf8 SourceFile
#41 = Utf8 Season.java
#42 = NameAndType #24:#25 // "<init>":()V
#43 = NameAndType #15:#16 // value:I
#44 = NameAndType #17:#18 // name:Ljava/lang/String;
#45 = Utf8 Season
#46 = Utf8 春天
#47 = NameAndType #24:#30 // "<init>":(ILjava/lang/String;)V
#48 = NameAndType #19:#20 // SPRING:LSeason;
#49 = Utf8 夏天
#50 = NameAndType #21:#20 // SUMMER:LSeason;
#51 = Utf8 秋天
#52 = NameAndType #22:#20 // AUTUMN:LSeason;
#53 = Utf8 冬天
#54 = NameAndType #23:#20 // WINTER:LSeason;
#55 = Utf8 java/lang/Object
{
// 这一部分我们感觉像是四个静态常量
public static final Season SPRING;
descriptor: LSeason;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
public static final Season SUMMER;
descriptor: LSeason;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
public static final Season AUTUMN;
descriptor: LSeason;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
public static final Season WINTER;
descriptor: LSeason;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
public int getValue();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field value:I
4: ireturn
LineNumberTable:
line 20: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LSeason;
public void setValue(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #2 // Field value:I
5: return
LineNumberTable:
line 24: 0
line 25: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this LSeason;
0 6 1 value I
public java.lang.String getName();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #3 // Field name:Ljava/lang/String;
4: areturn
LineNumberTable:
line 28: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LSeason;
public void setName(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #3 // Field name:Ljava/lang/String;
5: return
LineNumberTable:
line 32: 0
line 33: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this LSeason;
0 6 1 name Ljava/lang/String;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
0: new #4 // class Season
3: dup
4: iconst_1
// 以下就是给每个静态常量构造一个对象
// 但是我们发现人家调用的构造器是有一个参数的,String
5: ldc #5 // String 春天
7: invokespecial #6 // Method "<init>":(ILjava/lang/String;)V
10: putstatic #7 // Field SPRING:LSeason;
13: new #4 // class Season
16: dup
17: iconst_2
18: ldc #8 // String 夏天
20: invokespecial #6 // Method "<init>":(ILjava/lang/String;)V
23: putstatic #9 // Field SUMMER:LSeason;
26: new #4 // class Season
29: dup
30: iconst_3
31: ldc #10 // String 秋天
33: invokespecial #6 // Method "<init>":(ILjava/lang/String;)V
36: putstatic #11 // Field AUTUMN:LSeason;
39: new #4 // class Season
42: dup
43: iconst_4
44: ldc #12 // String 冬天
46: invokespecial #6 // Method "<init>":(ILjava/lang/String;)V
49: putstatic #13 // Field WINTER:LSeason;
52: return
LineNumberTable:
line 6: 0
line 7: 13
line 8: 26
line 9: 39
}
SourceFile: "Season.java"
这个是枚举的:
C:\Users\zn\IdeaProjects\untitled\out\production\untitled>javap -v SeasonEnum.class
Classfile /C:/Users/zn/IdeaProjects/untitled/out/production/untitled/SeasonEnum.class
Last modified 2021-8-28; size 974 bytes
MD5 checksum dc612af3d340c0984bbf18b7cffbf2e6
Compiled from "SeasonEnum.java"
public final class SeasonEnum extends java.lang.Enum<SeasonEnum>
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
Constant pool:
#1 = Fieldref #4.#42 // SeasonEnum.$VALUES:[LSeasonEnum;
#2 = Methodref #43.#44 // "[LSeasonEnum;".clone:()Ljava/lang/Object;
#3 = Class #23 // "[LSeasonEnum;"
#4 = Class #45 // SeasonEnum
#5 = Methodref #16.#46 // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#6 = Methodref #16.#47 // java/lang/Enum."<init>":(Ljava/lang/String;I)V
#7 = String #17 // SPRING
#8 = Methodref #4.#47 // SeasonEnum."<init>":(Ljava/lang/String;I)V
#9 = Fieldref #4.#48 // SeasonEnum.SPRING:LSeasonEnum;
#10 = String #19 // SUMMER
#11 = Fieldref #4.#49 // SeasonEnum.SUMMER:LSeasonEnum;
#12 = String #20 // AUTUMN
#13 = Fieldref #4.#50 // SeasonEnum.AUTUMN:LSeasonEnum;
#14 = String #21 // WINTER
#15 = Fieldref #4.#51 // SeasonEnum.WINTER:LSeasonEnum;
#16 = Class #52 // java/lang/Enum
#17 = Utf8 SPRING
#18 = Utf8 LSeasonEnum;
#19 = Utf8 SUMMER
#20 = Utf8 AUTUMN
#21 = Utf8 WINTER
#22 = Utf8 $VALUES
#23 = Utf8 [LSeasonEnum;
#24 = Utf8 values
#25 = Utf8 ()[LSeasonEnum;
#26 = Utf8 Code
#27 = Utf8 LineNumberTable
#28 = Utf8 valueOf
#29 = Utf8 (Ljava/lang/String;)LSeasonEnum;
#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 <clinit>
#39 = Utf8 Ljava/lang/Enum<LSeasonEnum;>;
#40 = Utf8 SourceFile
#41 = Utf8 SeasonEnum.java
#42 = NameAndType #22:#23 // $VALUES:[LSeasonEnum;
#43 = Class #23 // "[LSeasonEnum;"
#44 = NameAndType #53:#54 // clone:()Ljava/lang/Object;
#45 = Utf8 SeasonEnum
#46 = NameAndType #28:#55 // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#47 = NameAndType #33:#34 // "<init>":(Ljava/lang/String;I)V
#48 = NameAndType #17:#18 // SPRING:LSeasonEnum;
#49 = NameAndType #19:#18 // SUMMER:LSeasonEnum;
#50 = NameAndType #20:#18 // AUTUMN:LSeasonEnum;
#51 = NameAndType #21:#18 // WINTER:LSeasonEnum;
#52 = Utf8 java/lang/Enum
#53 = Utf8 clone
#54 = Utf8 ()Ljava/lang/Object;
#55 = Utf8 (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
{
// 静态常量
public static final SeasonEnum SPRING;
descriptor: LSeasonEnum;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final SeasonEnum SUMMER;
descriptor: LSeasonEnum;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final SeasonEnum AUTUMN;
descriptor: LSeasonEnum;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static final SeasonEnum WINTER;
descriptor: LSeasonEnum;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static SeasonEnum[] values();
descriptor: ()[LSeasonEnum;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #1 // Field $VALUES:[LSeasonEnum;
3: invokevirtual #2 // Method "[LSeasonEnum;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[LSeasonEnum;"
9: areturn
LineNumberTable:
line 1: 0
public static SeasonEnum valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)LSeasonEnum;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #4 // class 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 SeasonEnum
9: areturn
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 name Ljava/lang/String;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
0: new #4 // class 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:LSeasonEnum;
13: new #4 // class 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:LSeasonEnum;
26: new #4 // class 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:LSeasonEnum;
39: new #4 // class 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:LSeasonEnum;
52: iconst_4
53: anewarray #4 // class SeasonEnum
56: dup
57: iconst_0
58: getstatic #9 // Field SPRING:LSeasonEnum;
61: aastore
62: dup
63: iconst_1
64: getstatic #11 // Field SUMMER:LSeasonEnum;
67: aastore
68: dup
69: iconst_2
70: getstatic #13 // Field AUTUMN:LSeasonEnum;
73: aastore
74: dup
75: iconst_3
76: getstatic #15 // Field WINTER:LSeasonEnum;
79: aastore
80: putstatic #1 // Field $VALUES:[LSeasonEnum;
83: return
LineNumberTable:
line 2: 0
line 1: 52
}
Signature: #39 // Ljava/lang/Enum<LSeasonEnum;>;
SourceFile: "SeasonEnum.java"
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
常用方法
方法 | 说明 |
---|---|
values() 静态的自动生成的 | 可以遍历enum实例,其返回enum实例的数组 |
ordinal() 父类的实例方法 | 返回每个实例在声明时的次序 |
name() 父类的实例方法 | 返回enum实例声明时的名称 |
getDeclaringClass() | 返回其所属的enum类 |
valueOf() 静态的自动生成的 | 根据给定的名称返回相应的enum实例 |
public static void main(String[] args) {
SeasonEnum[] items = SeasonEnum.values();
for (int i = 0; i < items.length; i++) {
System.out.println(items[i].ordinal());
System.out.println(items[i].name());
System.out.println(items[i].getDeclaringClass());
System.out.println(SeasonEnum.valueOf(SeasonEnum.class, items[i].name()));
System.out.println("--------------------------");
}
}
结果:
0
SPRING
class SeasonEnum
SPRING
--------------------------
1
SUMMER
class SeasonEnum
SUMMER
--------------------------
2
AUTUMN
class SeasonEnum
AUTUMN
--------------------------
3
WINTER
class SeasonEnum
WINTER
--------------------------
2、Enum中添加新方法
- Enum 可以看做是一个常规类(除了不能继承自一个enum),enum 中可以添加方法和 main 方法。
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 String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
}
3、Switch语句中的Enum
- 正确用法
public static void main(String[] args) {
SeasonEnum season = SeasonEnum.SPRING;
switch (season){
case SPRING:
System.out.println("春天来了,又到了万物交配的季节!");
case SUMMER:
System.out.println("夏天来了,又可以穿大裤衩了!");
case AUTUMN:
System.out.println("秋天来了,又到了收获的季节!");
case WINTER:
System.out.println("冬天来了,又到了吃火锅的季节了!");
default:
System.out.println("也没有别的季节了。");
}
}
- 常规情况下必须使用 enum 类型来修饰 enum 实例,但在 case 语句中不必如此,
- 意思就是
case SPRING:
不需要写成case SeasonEnum.SPRING:
。
4、Enum的静态导入
- static import 可以将 enum 实例的标识符带入当前类,无需再用enum类型来修饰 enum 实例
import static com.ydlclass.SeasonEnum.*;
public class Test {
public static void main(String[] args) {
System.out.println(SPRING.name());
System.out.println(SUMMER.name());
}
}
5、枚举实现单例设计模式
目前我们的单例设计模式已经实现了三种了:
《Effective Java》
这种方法在功能上与公有域方法相近,但是它更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使是在面对复杂序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现 Singleton的最佳方法。—-《Effective Java 中文版 第二版》
package com.ydlclass;
public class Singleton {
private Singleton(){}
public static Singleton getInstant(){
return SingletonHolder.INSTANT.instant;
}
private enum SingletonHolder{
INSTANT;
private final Singleton instant;
SingletonHolder(){
instant = new Singleton();
}
}
public static void main(String[] args) {
System.out.println(Singleton.getInstant() == Singleton.getInstant());
}
}
6、枚举的优势
阿里《Java开发手册》对枚举的相关规定如下,我们在使用时需要稍微注意一下。
【强制】所有的枚举类型字段必须要有注释,说明每个数据项的用途。
【参考】枚举类名带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。说明:枚举其实就是特殊的常量类,且构造方法被默认强制是私有。正例:枚举名字为 ProcessStatusEnum 的成员名称:SUCCESS / UNKNOWN_REASON。
第一, int
类型本身并不具备安全性,假如某个程序员在定义 int
时少些了一个 final
关键字,那么就会存在被其他人修改的风险,而反观枚举类,它“天然”就是一个常量类,不存在被修改的风险(原因详见下半部分);
第二,使用 int
类型的语义不够明确,比如我们在控制台打印时如果只输出 1…2…3 这样的数字,我们肯定不知道它代表的是什么含义。
那有人就说了,那就使用常量字符呗,这总不会还不知道语义吧?实现示例代码如下:
public static final String COLOR_RED = "RED";
public static final String COLOR_BLUE = "BLUE";
public static final String COLOR_GREEN = "GREEN";
但是这样同样存在一个问题,有些初级程序员会不按套路出牌,他们可能会直接使用字符串的值进行比较,而不是直接使用枚举的字段,实现示例代码如下:
public class EnumTest {
public static final String COLOR_RED = "RED";
public static final String COLOR_BLUE = "BLUE";
public static final String COLOR_GREEN = "GREEN";
public static void main(String[] args) {
String color = "BLUE";
if ("BLUE".equals(color)) {
System.out.println("蓝色");
}
}
}
这样当我们修改了枚举中的值,那程序就凉凉了。
枚举比较推荐使用 ==