# 枚舉
[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進行選擇判斷。