使用 enum 列舉
June 11, 2022在〈介面語法細節〉談過,使用介面定義列舉常數的作法,現在已不鼓勵,建議使用 enum 語法。
enum 列舉
要定義列舉常數,使用 enum 關鍵字,直接來看範例:
package cc.openhome;
public enum Action {
STOP, RIGHT, LEFT, UP, DOWN
}
這是使用 enum 定義列舉常數最簡單的例子,STOP、RIGHT、LEFT、UP、DOWN 常數是 Action 實例,來看看如何運用:
package cc.openhome;
import static java.lang.System.out;
public class Game {
public static void main(String[] args) {
play(Action.RIGHT);
play(Action.UP);
}
public static void play(Action action) {
switch(action) {
case STOP: // 也就是Action.STOP
out.println("播放停止動畫");
break;
case RIGHT: // 也就是Action.RIGHT
out.println("播放向右動畫");
break;
case LEFT: // 也就是Action.LEFT
out.println("播放向左動畫");
break;
case UP: // 也就是Action.UP
out.println("播放向上動畫");
break;
case DOWN: // 也就是Action.DOWN
out.println("播放向下動畫");
break;
}
}
}
在這個範例中,play 方法中的 action 參數宣告為 Action 型態,只接受 Action 的實例,也就是只有 Action.STOP、Action.RIGHT、Action.LEFT、Action.UP、Action.DOWN 可以傳入,case 比對必須列舉 Action 的全部實例,編譯器在編譯時期會進行型態檢查,也就不需要特別定義 default。
成員的細節
每個列舉成員都會有個名稱與 int 值,可透過 name 方法取得名稱,適用於需要使用字串代表列舉值的場合,列舉的 int 值從 0 開始,依列舉順序遞增,可透過 ordinal 方法可取得,適用於需要使用 int 代表列舉值的場合。
列舉成員重新定義了 equals 與 hashCode,並標示為 final,實作邏輯與 Object 的 equals 與 hashCode 相同:
...略
public final boolean equals(Object other) {
return this==other;
}
public final int hashCode() {
return super.hashCode();
}
...略
列舉時可以定義方法,然而不能重新定義 equals 與 hashCode,這是因為列舉成員,在 JVM 只會存在單一實例,編譯器會如上產生 final 的 equals 與 hashCode,基於 Object 定義的 equals 與 hashCode,來比較物件相等性。
建構式、方法與介面
定義列舉時可以自定義建構式,條件是不得公開(public)或受保護(protected),也不可於建構式中呼叫 super。
來看個實際應用,先前談過 ordinal() 的值是依照成員順序,數值由 0 開始,若這不是你要的順序呢?例如原本有個 interface 定義的列舉常數:
public interface Priority {
int MAX = 10;
int NORM = 5;
int MIN = 1;
}
若現在想使用 enum 重新定義列舉,又必須與既存 API 搭配,也就是必須有個 int 值符合既存 API 的 Priority 值,這時怎麼辦?可以如下定義:
package cc.openhome;
public enum Priority {
MAX(10), NORM(5), MIN(1);
private int value;
private Priority(int value) {
this.value = value;
}
public int value() {
return value;
}
public static void main(String[] args) {
for(var priority : Priority.values()) {
System.out.printf("Priority(%s, %d)%n",
priority, priority.value());
}
}
}
在這邊建構式定義為 private,想在 enum 中呼叫建構式,只要在列舉成員後加上括號,就可以指定建構式的引數,你不能定義 name、ordinal 方法,它們是由編譯器產生,因此自定義了 value 方法來傳回 int 值。執行結果如下所示:
Priority(MAX, 10)
Priority(NORM, 5)
Priority(MIN, 1)
定義列舉時還可以實作介面,例如有個介面定義如下:
package cc.openhome;
public interface Command {
void execute();
}
若要在定義列舉時實作 Command 介面,基本方式可以如下:
public enum Action3 implements Command {
STOP, RIGHT, LEFT, UP, DOWN;
public void execute() {
switch(this) {
case STOP:
out.println("播放停止動畫");
break;
case RIGHT:
out.println("播放向右動畫");
break;
case LEFT:
out.println("播放向左動畫");
break;
case UP:
out.println("播放向上動畫");
break;
case DOWN:
out.println("播放向下動畫");
break;
}
}
}
基本上就是使用 enum 定義列舉時,使用 implements 實作介面,並實作介面定義的方法,就如同定義 class 時使用 implements 實作介面。
不過實作介面時,若希望各列舉成員可以有不同實作,例如上面程式片段中,其實是想讓列舉成員帶有各自的指令,目的是希望能如下執行程式:
package cc.openhome;
public class Game3 {
public static void play(Action3 action) {
action.execute();
}
public static void main(String[] args) {
Game3.play(Action3.RIGHT);
Game3.play(Action3.DOWN);
}
}
並可以有以下的執行結果:
播放右轉動畫
播放向下動畫
為了這個目的,先前實作 Command 時的 execute 方法時,使用了 switch 比對列舉實例,其實可以有更好的作法,就是定義 enum 時有個特定值類別本體(Value-Specific Class Bodies)語法,直接來看如何運用此語法:
package cc.openhome;
import static java.lang.System.out;
public enum Action3 implements Command {
STOP {
public void execute() {
out.println("播放停止動畫");
}
},
RIGHT {
public void execute() {
out.println("播放右轉動畫");
}
},
LEFT {
public void execute() {
out.println("播放左轉動畫");
}
},
UP {
public void execute() {
out.println("播放向上動畫");
}
},
DOWN {
public void execute() {
out.println("播放向下動畫");
}
};
}
可以看到在列舉成員後,直接加上 {} 實作 Command 的 execute 方法,這代表每個列舉實例會有不同的 execute 實作,在職責分配上,比 switch 的方式清楚許多。


