# 枚举
[TOC]
## 导学
在前端知识的学习中,我们遇到了一些控件,比如下拉框,单选框等。这些控件都是在提供一些数据,以供人们选择。实际上在Java中,我们也会存在这样的需求。比如如下的代码:
~~~Java
public class Weekday {
public static final int SUN = 0;
public static final int MON = 1;
public static final int TUE = 2;
public static final int WED = 3;
public static final int THU = 4;
public static final int FRI = 5;
public static final int SAT = 6;
}
~~~
我们定义了一个星期类。在该类中,我们定义了七个常量,分别用于表示从星期日到星期六。当在使用时,我们可以这么做:
~~~Java
int day = 5;
if(day == Weekday.SAT || day == Weekday.SUN) {
System.out.println("今天可以休息了!");
} else {
System.out.println("今天要上班!");
}
~~~
这种方法称作int枚举模式。可这样的代码,真的没有什么问题吗?假如上述的代码我们来变一变:
~~~Java
int day = 20;
if(day == Weekday.SAT || day == Weekday.SUN) {
System.out.println("今天可以休息了!");
} else {
System.out.println("今天要上班!");
}
int age = 8;
if(age > Weekday.SAT) {
System.out.println("我不再上幼儿园了!");
}
~~~
上述的代码在编译和运行上,都没有什么问题。但从修改之后的代码中,我们可以看出两个问题:
1. 注意到`Weekday`定义的常量范围是`0`~`6`,并不包含`20`,编译器无法检查超出举例范围的`int`值;
2. 定义的常量仍可与其他变量比较,但其用途并非是枚举星期值。
针对于这样的问题,我们希望能够有类似于下拉框,单选框这样的东西。让Java中的这样的数据能更好的被使用。这里,我们可以选择使用jdk 1.5中的枚举类。
## 枚举的定义
### 枚举的概念
>[info] 枚举是一个被命名的常数的集合,用于声明一组带标识符的常数。
枚举在曰常生活中很常见,例如一个人的性别只能是“男”或者“女”,一周的星期只能是 7 天中的一个等。类似这种当一个变量有几种固定可能的取值时,就可以将它定义为枚举类型。
在 JDK 1.5 之前没有枚举类型,那时候一般用接口常量来替代。而使用Java枚举类型 enum 可以更贴近地表示这种常量。其实对于枚举,可以将其简单的理解为key值唯一,value值也唯一的Map集合。
### 枚举的定义与使用
声明枚举时必须使用 enum 关键字,然后定义枚举的名称、可访问性、基础类型和成员等。枚举声明的语法如下:
~~~
enum-modifiers enum enumname {
enum-body,enum-body,enum-body,...enum-body;
}
~~~
其中,`enum-modifiers`表示枚举的修饰符主要包括` public`和`private`;`enumname` 表示声明的枚举名称;enum-body 表示枚举的成员,它是枚举类型的命名常数。
注意点:
1. 枚举类使用`enum`关键字去进行定义
2. 枚举类型中`enum-body`枚举字段的命名不允许相同。
3. 多个枚举字段使用逗号分隔,最后一个枚举字段使用分号结尾。
4. 枚举中的枚举名称,不需要使用修饰符去定义
5. 不需要使用数据类型定义,因为枚举中的枚举名称数据类型都是枚举自己
6. 最后一个枚举名称要使用分号做结尾
示例:
~~~
public enum Weekday {
SUN, MON, TUE, WED, THU, FRI, SAT;
}
~~~
## 枚举的编译
通过`enum`定义的枚举类,和其他的`class`有什么区别?
答案是没有任何区别。`enum`定义的类型就是`class`,只不过它有以下几个特点:
* 定义的`enum`类型总是继承自`java.lang.Enum`,且无法被继承;
* 只能定义出`enum`的实例,而无法通过`new`操作符创建`enum`的实例;
* 定义的每个实例都是引用类型的唯一实例;
* 可以将`enum`类型用于`switch`语句。
例如,我们定义的`Color`枚举类:
~~~
public enum Color {
RED, GREEN, BLUE;
}
~~~
编译器编译出的`class`大概就像这样:
~~~
public final class Color extends Enum { // 继承自Enum,标记为final class
// 每个实例均为全局唯一:
public static final Color RED = new Color();
public static final Color GREEN = new Color();
public static final Color BLUE = new Color();
// private构造方法,确保外部无法调用new操作符:
private Color() {}
}
~~~
## 枚举的自定义属性和自定义方法
在枚举中,可以自定义属性提供给枚举字段使用,但是从上面枚举的编译结果可以看出枚举字段默认调用的是私有的无参构造器,所以在枚举中定义自定义属性时,需要先定义有参构造器,才能提供是枚举字段使用,否则自定义属性的定义没有什么太大的意义。
~~~
public enum Weekday {
SUN("星期天",0),
MON("星期一",1),
TUE("星期二",2),
WED("星期三",3),
THU("星期四",4),
FRI("星期五",5),
SAT("星期六",6);
private String value;
private int key;
public String getValue() {
return value;
}
public int getKey() {
return key;
}
private Weekday() {}
private Weekday(String value, int key) {
this.value = value;
this.key = key;
}
}
~~~
使用:
~~~
public static void main(String[] args) {
Weekday day = Weekday.SUN;
System.out.println(day.getKey());//0
}
~~~
在字段的定义上,建议使用`final`修饰,但修饰以后,就不能构建无参构造器,所以看实际情况。
## 枚举的使用
* 作用一:用于规范数据的比较
枚举中的成员,它的类型就是枚举类型。
~~~
public class Main {
public static void main(String[] args) {
Weekday day = Weekday.SUN;
if (day == Weekday.SAT || day == Weekday.SUN) {
System.out.println("Work at home!");
} else {
System.out.println("Work at office!");
}
}
}
enum Weekday {
SUN, MON, TUE, WED, THU, FRI, SAT;
}
~~~
关于枚举的比较,可以使用`equals`也可以使用`==`,但是在源码中,使用的是`==`,所更推荐使用两个等号。
在此处,我们可以看到`day`变量的类型,就是`Weekday`。所以和`int`定义的常量相比,使用`enum`定义枚举有如下好处:
首先,`enum`常量本身带有类型信息,即`Weekday.SUN`类型是`Weekday`,编译器会自动检查出类型错误。例如,下面的语句不可能编译通过:
~~~
int day = 1;
if (day == Weekday.SUN) { // Compile error: bad operand types for binary operator '=='
}
~~~
* 作用二:用于限制数据的范围
~~~
public class ProjectConfig {
private HttpCode httpCode;
}
enum HttpCode {
H404("404"),
H500("500"),
H200("200");
private String code;
private HttpCode(String code) {
this.code = code;
}
}
~~~
## 枚举中的方法
#### name()
返回常量名,例如:
~~~
String s = Weekday.SUN.name(); // "SUN"
~~~
#### ordinal()
返回定义的常量的顺序,从0开始计数,例如:
~~~
int n = Weekday.MON.ordinal(); // 1
~~~
改变枚举常量定义的顺序就会导致`ordinal()`返回值发生变化。例如:
~~~
public enum Weekday {
SUN, MON, TUE, WED, THU, FRI, SAT;
}
~~~
和
~~~
public enum Weekday {
MON, TUE, WED, THU, FRI, SAT, SUN;
}
~~~
的`ordinal`就是不同的。如果在代码中编写了类似`if(x.ordinal()==1)`这样的语句,就要保证`enum`的枚举顺序不能变。新增的常量必须放在最后。
有些童鞋会想,`Weekday`的枚举常量如果要和`int`转换,使用`ordinal()`不是非常方便?比如这样写:
~~~
String task = Weekday.MON.ordinal() + "/ppt";
saveToFile(task);
~~~
但是,如果不小心修改了枚举的顺序,编译器是无法检查出这种逻辑错误的。要编写健壮的代码,就不要依靠`ordinal()`的返回值。而要靠给枚举字段设置属性。
#### switch
switch不是指枚举的方法,而是指枚举可以使用switch进行选择判断。