????枚舉類型是不可擴(kuò)展的盯蝴,但是接口類型是可擴(kuò)展的毅哗。使用接口,可以模擬可伸縮的枚舉捧挺。
????就幾乎所有方面來看虑绵,枚舉類型都優(yōu)越于類型安全枚舉模式。從表面上看闽烙,有一個(gè)異常與伸縮性有關(guān)翅睛,這個(gè)異常可能處在原來的模式中黑竞,卻沒有得到語言構(gòu)造的支持捕发。換句話說,使用這種模式很魂,就有可能讓一個(gè)枚舉類型去擴(kuò)展另一個(gè)枚舉類型扎酷;利用這種語言特性,則不可能這么做遏匆。這絕非偶然法挨。枚舉的可伸縮性最后證明基本上都不是什么好點(diǎn)子谁榜。擴(kuò)展類型的元素是基本類型的實(shí)例,基本類型的實(shí)例卻不是擴(kuò)展類型的元素凡纳,這樣很混亂窃植。
????類型安全枚舉模式:定義一個(gè)類來代表枚舉類型的單個(gè)元素,并且不提供任何公有的構(gòu)造函數(shù)荐糜。相反巷怜,提供公有的靜態(tài)final域,使枚舉類型中的每一個(gè)常量都對應(yīng)一個(gè)域暴氏。
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");
}
????要想類型安全模式可以被擴(kuò)展丛版,只要提供一個(gè)受保護(hù)的構(gòu)造函數(shù)就可以。然后其他人就可以擴(kuò)展這個(gè)類偏序,并且在子類中增加新的常量。類型安全枚舉模式可擴(kuò)展變形與可序列化變形是兼容的胖替,但是兩者組合的時(shí)候要非常小心研儒,每個(gè)子類必須分配自己的序數(shù),并且提供自己的readResolve方法独令。下邊的Operation類是可擴(kuò)展的端朵、可序列化的類型安全枚舉類(每個(gè),枚舉類型極其定義的枚舉變量在JVMh中都是唯一的):
public abstract class Operation1 implements Serializable{
private final transient String name;//標(biāo)記為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的一個(gè)子類燃箭,它增加了對數(shù)和指數(shù)的操作冲呢。他本身也是可以擴(kuò)展的。
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];
}
????有一種很好的方法來實(shí)現(xiàn)可伸縮性的枚舉招狸,以下是第30條中的Operation類型的擴(kuò)展版本:
/**
* 雖然枚舉類型是不能擴(kuò)展的 , 但是可以通過接口類表示API中的操作的接口類型 .
* 你可以定義一個(gè)功能完全不同的枚舉類型來實(shí)現(xiàn)這個(gè)接口 .
*/
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;
}
}
????你可以定義另外一個(gè)枚舉類型敬拓,它實(shí)現(xiàn)這個(gè)接口,并用這個(gè)新類型的實(shí)例代替基本類型裙戏。例如乘凸,假設(shè)你想要定義一個(gè)上述操作類型的擴(kuò)展,由求冪和求余操作組成累榜。你所要做的就是編寫一個(gè)枚舉類型营勤,讓它實(shí)現(xiàn)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;
}
}
????注意,在枚舉中壹罚,不必像在不可擴(kuò)展的枚舉中所做的那樣葛作,利用特定于實(shí)例的方法實(shí)現(xiàn)來聲明抽象的apply方法。這是因?yàn)槌橄蟮姆椒ㄊ墙涌诘囊徊糠帧?/p>
????不僅可以在任何需要“基本枚舉”的地方單獨(dú)傳遞一個(gè)"擴(kuò)展枚舉"的實(shí)例猖凛,而且除了那些基本類型的元素之外赂蠢,還可以傳遞完整的擴(kuò)展枚舉類型,并使用它的元素辨泳。通過下面這個(gè)測試程序客年,體驗(yàn)一下上面定義過的所有擴(kuò)展過的操作:
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>霞幅,這個(gè)有限的通配符類型,作為opSet參數(shù)的類型:
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));
}
}
}
????上面這兩段程序用命令行參數(shù) 2和4運(yùn)行時(shí)量瓜,都會產(chǎn)生這樣的輸出:
2.000000 ^ 4.000000= 16.000000
2.000000 % 4.000000= 2.000000
????用接口模擬可伸縮枚舉有個(gè)小小的不足司恳,即無法將實(shí)現(xiàn)從一個(gè)枚舉類型繼承到另一個(gè)枚舉類型。在上Operation的實(shí)例中绍傲,保存和獲取與某項(xiàng)操作相關(guān)聯(lián)的符號的邏輯代碼扔傅,可以復(fù)制到BasicOperation和ExtendedOperation中。在這個(gè)例子中是可以的烫饼,因?yàn)閺?fù)制的代碼非常少猎塞。如果共享功能比較多,則可以將它封裝在一個(gè)輔助類或者靜態(tài)輔助方法中杠纵,來避免代碼的復(fù)制工作荠耽。
????總而言之,雖然無法編寫可擴(kuò)展的枚舉類型比藻,卻可以通過編寫接口以及實(shí)現(xiàn)該接口的基礎(chǔ)枚舉類型铝量,對它進(jìn)行模擬。這樣允許客戶端編寫自己的枚舉來實(shí)現(xiàn)接口银亲。如果API是根據(jù)接口編寫的慢叨,那么在可以使用基礎(chǔ)枚舉類型的任何地方,也就可以使用這些枚舉务蝠。