第34條:用接口模擬可伸縮的枚舉

????枚舉類型是不可擴(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ǔ)枚舉類型的任何地方,也就可以使用這些枚舉务蝠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拍谐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子馏段,更是在濱河造成了極大的恐慌轩拨,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件院喜,死亡現(xiàn)場離奇詭異气嫁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)够坐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門寸宵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人元咙,你說我怎么就攤上這事梯影。” “怎么了庶香?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵甲棍,是天一觀的道長。 經(jīng)常有香客問我赶掖,道長感猛,這世上最難降的妖魔是什么七扰? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮陪白,結(jié)果婚禮上颈走,老公的妹妹穿的比我還像新娘。我一直安慰自己咱士,他們只是感情好立由,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著序厉,像睡著了一般锐膜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弛房,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天道盏,我揣著相機(jī)與錄音,去河邊找鬼文捶。 笑死荷逞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拄轻。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼伟葫,長吁一口氣:“原來是場噩夢啊……” “哼恨搓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起筏养,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤斧抱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后渐溶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辉浦,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年茎辐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宪郊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拖陆,死狀恐怖弛槐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情依啰,我是刑警寧澤乎串,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站速警,受9級特大地震影響叹誉,放射性物質(zhì)發(fā)生泄漏鸯两。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一长豁、第九天 我趴在偏房一處隱蔽的房頂上張望钧唐。 院中可真熱鬧,春花似錦蕉斜、人聲如沸逾柿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽机错。三九已至,卻和暖如春父腕,著一層夾襖步出監(jiān)牢的瞬間弱匪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工璧亮, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留萧诫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓枝嘶,卻偏偏與公主長得像帘饶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子群扶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 枚舉類型是指由一組固定的常量組成合法值的類型及刻,例如一年中的季節(jié),太陽系中的行星或者一副牌中的花色竞阐。在編程語言...
    小小輝_710a閱讀 1,426評論 0 0
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理缴饭,服務(wù)發(fā)現(xiàn),斷路器骆莹,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • Java 1.5發(fā)行版本新增了兩個(gè)引用類型家族:枚舉類型(Enumerate類)和注解類型(Annotation接...
    Timorous閱讀 403評論 0 0
  • { "Unterminated string literal.": "未終止的字符串文本幕垦。", "Identifi...
    一粒沙隨風(fēng)飄搖閱讀 10,383評論 0 3
  • 昨天晚上我們學(xué)習(xí)完了第一幕丢氢,復(fù)習(xí)完昨天學(xué)的知識:丹麥的一個(gè)老國王死了,本該是哈姆萊特來繼承王位先改,結(jié)果卻是他的叔父來...
    petermeng閱讀 343評論 3 5