列舉型態既然繼承自Enum的類別,除了由編譯器自動產生的private建構式之外,也可 以自行定義建構式,條件是不得為公開(public)建構式,也不可以於建構式中呼叫super()。
來看個實際應用,先前談過ordinal的值,會是使用enum列舉的成員順序,數值由0開始,如果這不是你想要的順序呢?例如原本有個 interface定義的列舉常數:
public
interface Priority {
int MAX = 10;
int NORM = 5;
int MIN = 1;
}
int MAX = 10;
int NORM = 5;
int MIN = 1;
}
若現在你想要使用enum重新定義列舉,但又必須與既存API搭配,也就是定義好的列舉實例,必須有個int值符合既存API的Priority值, 這時怎麼辦?可以如下定義:
- Priority.java
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(Priority priority : Priority.values()) {
System.out.printf("Priority(%s, %d)%n",
priority, priority.value());
}
}
}
在這邊建構式定義為private,在enum中呼叫建構式比較特別,直接在列舉成員後加上括號,就可以指定建構式需要的引數,由於Enum的 ordinal()被宣告為final,不能重新定義,所以自定義了value()方法來傳回int值。執行結果如下所示:
Priority(MAX,
10) Priority(NORM, 5) Priority(MIN, 1) |
可以看看Priority.class反編譯後的結果:
public final class Priority
extends Enum {
...略
private Priority(String s, int i, int value) {
super(s, i);
this.value = value;
}
public int value() {
return value;
}
...略
public static final Priority MAX;
public static final Priority NORM;
public static final Priority MIN;
private int value;
private static final Priority $VALUES[];
static
{
MAX = new Priority("MAX", 0, 10);
NORM = new Priority("NORM", 1, 5);
MIN = new Priority("MIN", 2, 1);
$VALUES = (new Priority[] {
MAX, NORM, MIN
});
}
}
...略
private Priority(String s, int i, int value) {
super(s, i);
this.value = value;
}
public int value() {
return value;
}
...略
public static final Priority MAX;
public static final Priority NORM;
public static final Priority MIN;
private int value;
private static final Priority $VALUES[];
static
{
MAX = new Priority("MAX", 0, 10);
NORM = new Priority("NORM", 1, 5);
MIN = new Priority("MIN", 2, 1);
$VALUES = (new Priority[] {
MAX, NORM, MIN
});
}
}
實際上你定義的建構式只是編譯器用來產生真正建構式時參考之用,你定義的value參數,編譯器會放在真正建構式的name與ordinal之後,真 正的 建構式會呼叫super()時傳入name與ordinal(所以你不可以在自定義建構式中呼叫super()),接著才是自定義建構式中的程式碼。在 static區塊中,編譯器仍自行維護name與ordinal的值,接著才是你呼叫自定義建構式時傳入的value值。
定義列舉時還可以實作介面,例如有個介面定義如下:
- Command.java
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:
System.out.println("播放停止動畫");
break;
case RIGHT:
System.out.println("播放向右動畫");
break;
case LEFT:
System.out.println("播放向左動畫");
break;
case UP:
System.out.println("播放向上動畫");
break;
case DOWN:
System.out.println("播放向下動畫");
break;
}
}
}
STOP, RIGHT, LEFT, UP, DOWN;
public void execute() {
switch(this) {
case STOP:
System.out.println("播放停止動畫");
break;
case RIGHT:
System.out.println("播放向右動畫");
break;
case LEFT:
System.out.println("播放向左動畫");
break;
case UP:
System.out.println("播放向上動畫");
break;
case DOWN:
System.out.println("播放向下動畫");
break;
}
}
}
基本上就是使用enum定義列舉時,使用implements實作介面,並將介面定義的方法實作,就如同定義class時使用implements實 作介面。
不過如果在實作介面,希望各列舉成員可以有不同實作,例如上面程式片段中,其實你想讓列舉成員不僅有各自列舉實例,還可以帶有各自的可執行指令,也就 是希望可以如下執行程式:
- Game.java
package cc.openhome;
public class Game {
public static void play(Action action) {
action.execute();
}
public static void main(String[] args) {
Game3.play(Action.RIGHT);
Game3.play(Action.DOWN);
}
}
希望可以有以下的執行結果:
播
放右轉動畫 播放向下動畫 |
為了這個目的,先前實作Command時的execute()方法時,是使用switch比對列舉實例,但其實可以有更好的作法,就是定義enum時 有個特定值類別本體(Value-Specific Class Bodies)語法,直接來看如何運用此語法:
- Action.java
package cc.openhome;
public enum Action implements Command {
STOP {
public void execute() {
System.out.println("播放停止動畫");
}
},
RIGHT {
public void execute() {
System.out.println("播放右轉動畫");
}
},
LEFT {
public void execute() {
System.out.println("播放左轉動畫");
}
},
UP {
public void execute() {
System.out.println("播放向上動畫");
}
},
DOWN {
public void execute() {
System.out.println("播放向下動畫");
}
};
}
可以看到在列舉成員後,直接加上{}實作Command的execute()方法,這代表著每個列舉實例都會有不同的execute()實作,在職責 分配上,比switch的方式清楚許多。
實際上,編譯器會將Action標示為抽象類別:
public abstract class Action
extends Enum implements Command {
...
}
...
}
並為每個列舉成員後的{}語法,產生匿名內部類別,這個匿名內部類別繼承了Action,實作了execute()方法:
...略
static
{
STOP = new Action3("STOP", 0) {
public void execute() {
System.out.println("\u64AD\u653E\u505C\u6B62\u52D5\u756B");
}
};
RIGHT = new Action3("STOP", 0) {
public void execute() {
System.out.println("\u64AD\u653E\u505C\u6B62\u52D5\u756B");
}
};
...略
}
...略
所以每個列舉成員,實際上都參考至Action的匿名子類別。瞭解這個原理後,也就可以知道,特定值類別本體語法不僅在實作介面時可以使用,也可以運 用在重新定義父類別方法。例如重新定義toString(),以先前Priority為例,可改寫為以下:
- Priority.java
package cc.openhome;
public enum Priority {
MAX(10) {
public String toString() {
return String.format("(%2d) - 最大權限", value);
}
},
NORM(5) {
public String toString() {
return String.format("(%2d) - 普通權限", value);
}
},
MIN(1) {
public String toString() {
return String.format("(%2d) - 最小權限", value);
}
};
protected int value;
private Priority(int value) {
this.value = value;
}
public int value() {
return value;
}
public static void main(String[] args) {
for(Priority priority : Priority.values()) {
System.out.println(priority);
}
}
}
執行結果如下:
(10) - 最大權限 ( 5) - 普通權限 ( 1) - 最小權限 |
為