枚举类

引言:枚举类;补充了一些额外知识点

枚举类

为什么要使用枚举类?

  1. 安全性(使用枚举类不会被反射获取,不可以与其他类型的值进行比较,防止错误)
  2. 易读性(枚举类比原本使用数字可以携带更多的信息,尤其是在日志记录时,更加容易)

Enum类的核心API

所有枚举类,默认继承自java.lang.Enum,该类有以下几个方法

1
2
3
String name() //返回枚举类常量的名称(即指定的String)
String toString() //返回枚举类常量的名称
int ordinal()//返回枚举常量的序数,取决于创建枚举类的位置

注意:

  • 尽量使用name()方法来返回字符串等效串,而不去使用toString

为什么要使用name()

因为你不能保证toString没有被重写!

导致此枚举类在序列化时有可能使字符串失去等效性,而使用name()就没有这种情况

一个枚举的Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public enum Color {
RED("红色", 1),
GREEN("绿色", 2),
BLANK("白色", 3),
YELLO("黄色", 4); // 注意使用分号隔开
// 成员变量
private String name;
private int index;
// 构造方法,默认就是私有的,枚举类不允许外部创建
private Color(String name, int index) {
this.name = name;
this.index = index;
}
//覆盖方法
@Override
public String toString() {
return "Color{" +
"name='" + name + '\'' +
", index=" + index +
'}';
}
}

测试一下API会返回什么?

1
2
3
4
Color color = Color.BLANK;
System.out.println(color.name()); // BLANK
System.out.println(color.toString()); // Color{name='白色', index=3}
System.out.println(color.ordinal()); // 2

枚举类的特点

  • 枚举类继承自java.lang.Enum(因此枚举类不能继承其他类,但是可以实现接口)
  • 无法使用new创建实例(因为构造方法是私有的)
  • 定义的每个实例都是引用类型的唯一实例(唯一一个安全的构造单例模式的方法)
  • 可以在switch语句中使用

枚举类的本质

我们自己建立的枚举类

1
2
3
public enum Weekday {
SUN, MON, TUE, WED, THU, FRI, SAT;
}

在被编译器编译后是这样

1
2
3
4
5
6
7
8
9
10
11
12
13
public final class Weekday extends Enum { // 继承自Enum,标记为final class
// 每个实例均为全局唯一:
public static final Weekday SUN = new Weekday();
public static final Weekday MON = new Weekday();
public static final Weekday TUE = new Weekday();
public static final Weekday WED = new Weekday();
public static final Weekday THU = new Weekday();
public static final Weekday FRI = new Weekday();
public static final Weekday SAT = new Weekday();

// private构造方法,确保外部无法调用new操作符:
private Color() {}
}

由于ordinal()方法依赖于排序树,所以改变枚举类常量的顺序就会发生变化,不利于程序的鲁棒性

建议枚举类代码写为

1
2
3
4
5
6
7
8
public enum Weekday {
SUN(0), MON(1), TUE(2), WED(3), THU(4), FRI(5), SAT(6);
public final int dayValue;
//这里建议使用final修饰
Weekday(int dayValue) {
this.dayValue = dayValue;
}
}

判断枚举常量的名字,要始终使用name()方法,绝不能调用toString()!

在switch语句中使用

switch()语句支持的类型有:
byte char short int及其包装类、以及枚举类String

有关switch这方面推荐博客CSDN博主 由零开始Leon

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Weekday day = Weekday.SUN;
switch(day) {
case MON:
case TUE:
case WED:
case THU:
case FRI:
System.out.println("Today is " + day + ". Work at office!");
break;
case SAT:
case SUN:
System.out.println("Today is " + day + ". Work at home!");
break;
default:
throw new RuntimeException("cannot process " + day);

枚举的values()方法

枚举类都有一个values方法,但注意,这个方法并不来自父类Enum,而是编译器增加的一个方法

The compiler automatically adds some special methods when it creates an enum. For example, they have a static values method that returns an array containing all of the values of the enum in the order they are declared. (Oracle官方文档

翻译:编译器在创建enum时,会自动添加一些方法。比如values静态方法,会返回一个该枚举类型的数组

工作中经常会遇到一个枚举类,需要通过Int获取他的String(或是相反)的情况,这里给出一个模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public enum EnumTemplate {
CREATE(0,"create"),
TOUCH(1,"touch"),
SUBMIT(2, "submit");

private String tag;
private Integer id;

EnumTemplate(Integer id, String tag) {
this.tag = tag;
this.id = id;
}

/**
* 通过int获取String(普通写法)
*/
public static String getNameById(Integer id){
for (EnumTemplate value : values()) {
if(value.id.equals(id)){
return value.tag;
}
}
return "";
}

/**
* 通过String获取int(流式写法)
*/
public static Integer getIdByName(String name){
return Arrays.stream(values())
.collect(Collectors.toList())
.stream()
.filter(x -> name.equals(x.tag))
.map(EnumTemplate::getId)
.findFirst().orElse(0);
}

public String getTag() {
return tag;
}

public Integer getId() {
return id;
}
}

注意:工作强烈建议使用普通方法而不是流式写法,效率差太多了(差距一百多倍)。

枚举的更多写法

接口的内部枚举

有时候我们会遇到这种情况,枚举有了分类,比如:腾讯有QQ与微信,QQ有QQ登录的和平精英、王者荣耀,微信同样也有,但是我们想用枚举标识这两种不同的账号,我们可以如此创建这个枚举类。

同一个接口Tencent下,实现了两个枚举类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public interface Tencent {
enum WX{
WX_WZRY(0, "王者荣耀"),
WX_HPJY(1, "和平精英");
private Integer id;
private String name;

WX(Integer id, String name) {
this.id = id;
this.name = name;
}

public Integer getId() {
return id;
}

public String getName() {
return name;
}
}

enum QQ{
QQ_WZRY(0, "王者荣耀"),
QQ_HPJY(1, "和平精英");
private Integer id;
private String name;

QQ(Integer id, String name) {
this.id = id;
this.name = name;
}

public Integer getId() {
return id;
}

public String getName() {
return name;
}
}
}

在调用时,我们需要通过接口名调用,才能拿到想要的数据:

1
TencentEnum.WX.WX_HPJY.getName();

接口实现抽象方法

枚举类可以有抽象方法,并且需要在实例中实现。

假设现在需要给和平精英的玩家发红包,但是发的个数不同,就可以这么写:

(但这种写法具体有什么好处,有待考究)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public enum TencentEnum2 {
WX_HPJY(0, "和平精英"){
private final Integer num = 100;
@Override
public int sendHongBao() {
return num;
}
},
QQ_HPJY(1, "和平精英"){
private final Integer num = 1;
@Override
public int sendHongBao() {
return num;
}
},
WX_WZRY(2, "王者荣耀"){
@Override
public int sendHongBao() {
return 0;
}
};

private Integer id;
private String name;

abstract public int sendHongBao();

TencentEnum2(Integer id, String name) {
this.id = id;
this.name = name;
}
}

参考材料: 廖雪峰官方网站
官方API
CSDN博主 由零开始Leon