????枚舉類型是不可擴展的,但是接口類型是可擴展的。使用接口,可以模擬可伸縮的枚舉。
????就幾乎所有方面來看,枚舉類型都優越于類型安全枚舉模式。從表面上看,有一個異常與伸縮性有關,這個異??赡芴幵谠瓉淼哪J街?,卻沒有得到語言構造的支持。換句話說,使用這種模式,就有可能讓一個枚舉類型去擴展另一個枚舉類型;利用這種語言特性,則不可能這么做。這絕非偶然。枚舉的可伸縮性最后證明基本上都不是什么好點子。擴展類型的元素是基本類型的實例,基本類型的實例卻不是擴展類型的元素,這樣很混亂。
????類型安全枚舉模式:定義一個類來代表枚舉類型的單個元素,并且不提供任何公有的構造函數。相反,提供公有的靜態final域,使枚舉類型中的每一個常量都對應一個域。
public class Suit {
private final String name;
private Suit(String name){
this.name = name;
}
public String toString(){
return name;
}
public static final Suit CLIBS = new Suit("clubs");
public static final Suit DIAMONDS = new Suit("diamonds");
public static final Suit HEARTS = new Suit("hearts");
public static final Suit SPADES = new Suit("spades");
}
????要想類型安全模式可以被擴展,只要提供一個受保護的構造函數就可以。然后其他人就可以擴展這個類,并且在子類中增加新的常量。類型安全枚舉模式可擴展變形與可序列化變形是兼容的,但是兩者組合的時候要非常小心,每個子類必須分配自己的序數,并且提供自己的readResolve方法。下邊的Operation類是可擴展的、可序列化的類型安全枚舉類(每個,枚舉類型極其定義的枚舉變量在JVMh中都是唯一的):
public abstract class Operation1 implements Serializable{
private final transient String name;//標記為transient的變量不會被序列化
protected Operation1(String name) {
this.name = name;
}
public String toString(){
return this.name;
}
public static Operation1 PLUS = new Operation1("+"){
protected double eval(double x, double y) {
return x + y;
}
};
public static Operation1 MINUS = new Operation1("-"){
protected double eval(double x, double y) {
return x - y;
}
};
public static Operation1 TIMES = new Operation1("*"){
protected double eval(double x, double y) {
return x * y;
}
};
public static Operation1 DIVIDE = new Operation1("/"){
protected double eval(double x, double y) {
return x / y;
}
};
protected abstract double eval(double x, double y);
private static int nextOrdinal = 0;
private final int ordinal = nextOrdinal++;
private static final Operation1[] VALUES ={PLUS,MINUS,TIMES,DIVIDE};
Object readResolve() throws ObjectStreamException{
return VALUES[ordinal];
}
????下面是Operation的一個子類,它增加了對數和指數的操作。他本身也是可以擴展的。
public abstract class ExtendedOperation1 extends Operation1 {
ExtendedOperation1(String name) {
super(name);
}
public static Operation1 LOG = new Operation1("log"){
protected double eval(double x, double y) {
return Math.log(y)/Math.log(x);
}
};
public static Operation1 EXP = new Operation1("exp"){
protected double eval(double x, double y) {
return x + y;
}
};
private static int nextOrdinal = 0;
private final int ordinal = nextOrdinal++;
private static final Operation1[] VALUES = {LOG,EXP};
Object readResolve() throws ObjectStreamException{
return VALUES[ordinal];
}
????有一種很好的方法來實現可伸縮性的枚舉,以下是第30條中的Operation類型的擴展版本:
/**
* 雖然枚舉類型是不能擴展的 , 但是可以通過接口類表示API中的操作的接口類型 .
* 你可以定義一個功能完全不同的枚舉類型來實現這個接口 .
*/
public interface Operation {
double apply(double x,double y);
}
public enum BasicOperation implements Operation{
PLUS("+"){
public double apply(double x, double y) {
return x + y;
}
},
MINUS("-"){
public double apply(double x, double y) {
return x - y;
}
},
TIMES("*"){
public double apply(double x, double y){
return x * y;
}
},
DIVIDE("/"){
public double apply(double x, double y) {
return x / y;
}
};
private final String symbol;
BasicOperation(String symbol) {
this.symbol = symbol;
}
}
????你可以定義另外一個枚舉類型,它實現這個接口,并用這個新類型的實例代替基本類型。例如,假設你想要定義一個上述操作類型的擴展,由求冪和求余操作組成。你所要做的就是編寫一個枚舉類型,讓它實現Operation接口:
public enum ExtendedOperation implements Operation {
EXP("^"){
@Override
public double apply(double x, double y) {
return Math.pow(x , y);
}
},
REMAINDER("%"){
@Override
public double apply(double x, double y) {
return x % y;
}
};
private final String symbol;
ExtendedOperation(String symbol) {
this.symbol = symbol;
}
}
????注意,在枚舉中,不必像在不可擴展的枚舉中所做的那樣,利用特定于實例的方法實現來聲明抽象的apply方法。這是因為抽象的方法是接口的一部分。
????不僅可以在任何需要“基本枚舉”的地方單獨傳遞一個"擴展枚舉"的實例,而且除了那些基本類型的元素之外,還可以傳遞完整的擴展枚舉類型,并使用它的元素。通過下面這個測試程序,體驗一下上面定義過的所有擴展過的操作:
public class test1 {
public static void main(String[] args) {
double x = 2;
double y = 4;
test(ExtendedOperation.class, x, y);
}
private static <T extends Enum<T> & Operation> void test(Class<T> opSet,
double x, double y) {
for (Operation op : opSet.getEnumConstants())
System.out.printf("%f %s %f= %f%n", x, op, y, op.apply(x, y));
}
}
????第二種方法是使用Collection<? extends Operation>,這個有限的通配符類型,作為opSet參數的類型:
import java.util.Arrays;
import java.util.Collection;
public class test2 {
public static void main(String[] args) {
double x = 2;
double y = 4 ;
test(Arrays.asList(ExtendedOperation.values()),x,y);
}
private static void test(Collection<? extends Operation> opSet,double x, double y) {
for (Operation op : opSet) {
System.out.printf("%f %s %f= %f%n", x, op, y, op.apply(x, y));
}
}
}
????上面這兩段程序用命令行參數 2和4運行時,都會產生這樣的輸出:
2.000000 ^ 4.000000= 16.000000
2.000000 % 4.000000= 2.000000
????用接口模擬可伸縮枚舉有個小小的不足,即無法將實現從一個枚舉類型繼承到另一個枚舉類型。在上Operation的實例中,保存和獲取與某項操作相關聯的符號的邏輯代碼,可以復制到BasicOperation和ExtendedOperation中。在這個例子中是可以的,因為復制的代碼非常少。如果共享功能比較多,則可以將它封裝在一個輔助類或者靜態輔助方法中,來避免代碼的復制工作。
????總而言之,雖然無法編寫可擴展的枚舉類型,卻可以通過編寫接口以及實現該接口的基礎枚舉類型,對它進行模擬。這樣允許客戶端編寫自己的枚舉來實現接口。如果API是根據接口編寫的,那么在可以使用基礎枚舉類型的任何地方,也就可以使用這些枚舉。