使用 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
的方式清楚許多。