第六章料按、枚舉和注解

Java 1.5發(fā)行版本新增了兩個(gè)引用類型家族:枚舉類型(Enumerate類)和注解類型(Annotation接口)蛤铜。

第三十條蓬痒、用enum代替int常量

  1. 枚舉類型是指由一組固定的常量組成合法值的類型。替代之前的具名的int常量闯睹。
    public static final int APPLE_FUJI = 0;

    這種方式稱作int枚舉模式戏羽,存在諸多的不足:
    程序十分脆弱,因?yàn)閕nt枚舉是編譯時(shí)常量楼吃,被編譯到使用它們的客戶端中始花。如果與枚舉常量關(guān)聯(lián)的int發(fā)生了變化,客戶端就必須重新編譯所刀,否則衙荐,程序行為會(huì)變得不確定性饺藤。(還有個(gè)變體是String枚舉模式)

  2. java的枚舉類型是功能十分齊全的類耻矮,本質(zhì)上是int值。
    基本想法是:通過公有的靜態(tài)final域?yàn)槊總€(gè)枚舉常量導(dǎo)出實(shí)例的類曾撤。枚舉類型是實(shí)例受控的斩披,它們是單例的泛型化溜族,本質(zhì)上是單元素的枚舉讹俊。

    枚舉提供了編譯時(shí)的類型安全,如果聲明一個(gè)參數(shù)的類型為Apple煌抒,就可以保證仍劈,被傳到該參數(shù)上的任何非null的對(duì)象引用一定屬于三個(gè)有效的Apple值之一。試圖傳遞類型錯(cuò)誤的值時(shí)寡壮,會(huì)導(dǎo)致編譯時(shí)錯(cuò)誤贩疙。
    public enum Apple{FUJI,PIPPIN,GERNNY_SMITH}
    包含同名變量的多個(gè)枚舉類型可以在一個(gè)系統(tǒng)中和平共處,因?yàn)槊總€(gè)類型都有自己的命名空間况既。

  3. 除了彌補(bǔ)int枚舉常量的不足这溅,枚舉類型還允許添加任意的方法和域(近似于類),并實(shí)現(xiàn)任意的接口棒仍。它們提供了所有Object方法的高級(jí)實(shí)現(xiàn)悲靴,實(shí)現(xiàn)了Comparable和Serializable接口。我們?yōu)樯兑獙⒎椒ɑ蛘哂蚣尤氲矫杜e類型中莫其?將數(shù)據(jù)與它的常量關(guān)聯(lián)起來癞尚。可以用任何適當(dāng)?shù)姆椒▉碓鰪?qiáng)枚舉類型乱陡。

     /**
      * Created by laneruan on 2017/7/10.
      * 一個(gè)枚舉類型的例子
      * 為了將數(shù)據(jù)與枚舉常量關(guān)聯(lián)起來浇揩,得聲明實(shí)例域,并編寫一個(gè)帶有數(shù)據(jù)并將數(shù)據(jù)保存在域中的構(gòu)造器蛋褥。
      */
     public class EnumPlanet {
         public enum Planet{
             MERCURY(3.302e+23,2.439e6),
             VENUS(4.8693+24,6.052e6),
             EARTH(5.975e+24,6.378e6),
             MARS(6.419e+23,3.393e6),
             JUPITER(1.899e+27,7.149e7),
             SATURN(5.685e+26,6.027e7),
             URANUS(8.683e+25,2.556e7),
             NEPTUNE(1.024e+26,2.477e7);
             private final double mass;
             private final double radius;
             private final double surfaceGravity;
             private static final double G = 6.67300E-11;
     
             Planet(double mass,double radius){
                 this.mass = mass;
                 this.radius = radius;
                 surfaceGravity = G * mass/(radius * radius);
             }
             public double mass(){return mass;}
             public double radius(){return radius;}
             public double surfaceGravity(){return surfaceGravity;}
     
             public double surfaceWeight(double mass){
                 return mass * surfaceGravity;
             }
         }
         public static void main(String[] args){
             double earthWeight = Double.parseDouble(args[0]);
             double mass = earthWeight/Planet.EARTH.surfaceGravity();
             for (Planet p : Planet.values()){
                 System.out.println("Weight on " + p +" is "+p.surfaceWeight(mass));
             }
         }
     }
    

    與枚舉常量關(guān)聯(lián)的有些行為临燃,可能只需要用在定義了枚舉的類或者包中,這種行為最好被實(shí)現(xiàn)成私有的或者包級(jí)私有的方法烙心。
    如果一個(gè)枚舉具有普遍適用性膜廊,它就應(yīng)該成為一個(gè)頂層類。如果它是被用在一個(gè)特定的頂層類中淫茵,它就應(yīng)該成為該頂層類的一個(gè)成員類爪瓜。

  4. 特定于常量的方法實(shí)現(xiàn):將不同的行為與每個(gè)枚舉常量關(guān)聯(lián)起來

     public enum Operation{
         PLUS{
             @Override
             double apply(double x, double y) {
                 return x+y;
             }
         },
         MINUS{
             @Override
             double apply(double x, double y) {
                 return x-y;
             }
         },
         TIMES{
             @Override
             double apply(double x, double y) {
                 return x*y;
             }
         },
         DIVIDE{
             @Override
             double apply(double x, double y) {
                 return x/y;
             }
         };
         abstract double apply(double x,double y),
     }
    
  5. 什么時(shí)候應(yīng)該使用枚舉類型匙瘪?
    每當(dāng)需要一組固定常量的時(shí)候铆铆。包括:天然的枚舉類型;在編譯的時(shí)候就知道其所有可能值的集合丹喻。

  6. 總結(jié):與int常量相比薄货,枚舉類型的優(yōu)勢(shì)不言而喻:易讀,更加安全碍论。功能更加強(qiáng)大谅猾。許多枚舉都不需要顯式的構(gòu)造器或者成員,但許多其他枚舉類型則受益于“每個(gè)常量與屬性的關(guān)聯(lián)”以及“提供行為受這個(gè)屬性影響的方法”。


第三十一條税娜、用實(shí)例域代替序數(shù)

許多枚舉天生就與一個(gè)單獨(dú)的int值相關(guān)聯(lián)坐搔,所有的枚舉都有一個(gè)ordinal方法,它返回每個(gè)枚舉常量在類型中的數(shù)字位置敬矩「判校可以試著從序數(shù)中得到關(guān)聯(lián)的int值。最好避免使用ordinal方法弧岳。

永遠(yuǎn)不要根據(jù)枚舉的序數(shù)導(dǎo)出與它想關(guān)聯(lián)的值凳忙,而是要將它保存在一個(gè)實(shí)例域中:

    public enum Ensemble{
        SOLO(1),DUET(2),TRIO(3),QUARTET(4),QUINTET(5),
        SETET(6),SEPET(7),OCTET(8),NONET(9),DECTET(10);
        private final int numberOfMusicians;
        Ensemble(int size){
            this.numberOfMusicians = size;
        }
        public int numberOfMusicians(){return numberOfMusicians;}
    //   public int numberOfMusicians(){return ordinal()+1;}
    }

第三十二條、用EnumSet代替位域

需要傳遞多組常量集時(shí)禽炬,java.util包提供了EnumSet類來有效地表示從單個(gè)枚舉類型中提取多個(gè)值的多個(gè)集合消略,這個(gè)類實(shí)現(xiàn)了Set接口,提供了豐富的功能瞎抛。

    class Text{
        public enum Style {BOLD,ITALIC,UNDERLINE,STRIKETHROUGH}
        public void applyStyles(Set<Style> styles){}
    }
    text.applyStyles(EnumSet.of(Style.BOLD,Style.ITALIC));

總結(jié):正是因?yàn)槊杜e類型要用在集合中,所以沒有理由用位域來表示他却紧。


第三十三條桐臊、用EnumMap來代替序數(shù)索引

最好不要用序數(shù)來索引數(shù)組,而要使用EnumMap晓殊。

import java.util.EnumMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 假設(shè)有個(gè)香草的數(shù)組断凶,表示一座花園中的植物,想要按照類型(一年生巫俺、兩年生认烁、多年生)
 * 進(jìn)行組織之后將這些植物列出來。
 * 注意的是:EnumMap采用了鍵類型的Class對(duì)象構(gòu)造器:這是一個(gè)有限制的類型令牌介汹,
 * 提供了運(yùn)行時(shí)的泛型信息却嗡。
 */
public class EnumMapHerb {
    public enum Type{ANNUAL,PERENNIAL,BIENNIAL}

    private final String name;
    private final Type type;

    EnumMapHerb(String name,Type type){
        this.name = name;
        this.type = type;
    }
    @Override
    public String toString(){
        return name;
    }
    public static void main(String[] args){
        EnumMapHerb[] garden = null;
        Map<Type,Set<EnumMapHerb>> herbByType =
                new EnumMap<Type, Set<EnumMapHerb>>(EnumMapHerb.Type.class);
        for(EnumMapHerb.Type t :EnumMapHerb.Type.values()){
            herbByType.put(t,new HashSet<EnumMapHerb>());
        }
        for(EnumMapHerb h:garden){
            herbByType.get(h.type).add(h);
        }
    }
}

第三十四條、用接口模擬可伸縮的枚舉

  1. 以操作碼opcode為例:它的元素表示在某種機(jī)器上的那些操作嘹承。有時(shí)候窗价,要盡可能地讓API的用戶提供他們自己的操作,這樣可以有效地?cái)U(kuò)展API提供的操作集叹卷『掣郏可以利用枚舉類型來實(shí)現(xiàn)這種效果:

     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;
         }
         @Override
         public String toString(){
             return symbol;
         }
     }
    

    雖然枚舉類型BasicOperaion不是可擴(kuò)展的,但是接口類型是可擴(kuò)展的骤竹,你可以定義另一個(gè)枚舉類型帝牡,它實(shí)現(xiàn)這個(gè)接口,并用這個(gè)新類型的實(shí)例代替基本類型蒙揣。

     public enum ExtendOperation implements Operation{
         EXP("^"){
             public double apply(double x,double y){
                 return Math.pow(x,y);
             }
         },
         REMAINDER("%"){
             public double apply(double x,double y){
                 return x % y;
             }
         };
         private final String symbol;
         ExtendOperation(String symbol){
             this.symbol =  symbol;
         }
         @Override
         public String toString(){
             return symbol;
         }
     }
    

    以下是該類型的使用:

     public static void main(String[] args){
         double x = Double.parseDouble(args[0]);
         double y = Double.parseDouble(args[1]);
         test(ExtendOperation.class,x,y);
     }
     //很復(fù)雜的聲明確保了對(duì)象既表示枚舉又表示Operation的子類型
     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è)有限制的通配符類型:

     public static void main(String[] args){
         double x = Double.parseDouble(args[0]);
         double y = Double.parseDouble(args[1]);
         test(Arrays.asList(ExtendOperation.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));
         }
     }
    

這樣得到的代碼不復(fù)雜且更靈活:允許操作者將多個(gè)實(shí)現(xiàn)類型的操作合并到一起。

  1. 總結(jié):雖然無法編寫可擴(kuò)展的枚舉類型鸣奔,但可以通過編寫接口以及實(shí)現(xiàn)該接口的基礎(chǔ)枚舉類型墨技,對(duì)它進(jìn)行模擬惩阶。

第三十五條、注解優(yōu)先于命名模式(Naming Pattern)

  1. Java 1.5之前一般使用命名模式表明有些程序元素需要通過某種工具或者框架進(jìn)行特殊處理扣汪。這種模式缺點(diǎn)很明顯:文字拼寫錯(cuò)誤很容易發(fā)生且難以發(fā)覺断楷;無法確保它們只用于相應(yīng)的程序元素上;沒有提供將參數(shù)值與程序元素相關(guān)聯(lián)起來的好方法崭别。

  2. 注解很好地解決了這些問題:
    假設(shè)想要定義一個(gè)注解類型RunTests來指定簡(jiǎn)單的測(cè)試冬筒,它們自動(dòng)運(yùn)行,并在拋出異常時(shí)失斆┲鳌:

     import java.lang.annotation.*;
     @Retention(RetentionPolicy.RUNTIME) //表明注解應(yīng)在運(yùn)行時(shí)保留舞痰,元注解
     @Target(ElementType.METHOD)  //表明只有在方法中聲明才是合法的,元注解
     public @interface RunTests {
     }
    
  3. 現(xiàn)實(shí)應(yīng)用中的Test注解诀姚,稱作標(biāo)記注解(marker annotation)响牛。

  4. 總結(jié):大多數(shù)程序員不需要定義注解類型,所有的程序員都應(yīng)該使用Java平臺(tái)所提供的預(yù)定義的注解類型赫段,還要考慮使用IDE或者靜態(tài)分析工具所提供的任何注解呀打。

第三十六條、堅(jiān)持使用@Override注解

  1. Override注解只能在方法聲明時(shí)使用糯笙,表示被注解的方法聲明覆蓋了超類中的一個(gè)聲明贬丛。堅(jiān)持使用這一注解,可以防止一大類的非法錯(cuò)誤给涕。

  2. 現(xiàn)代的IDE提供了堅(jiān)持使用@Override的另一種理由:IDE具有自動(dòng)檢查功能豺憔,稱作代碼檢驗(yàn)(code inspection),當(dāng)有一個(gè)方法沒有@Override注解卻覆蓋了超類方法時(shí)够庙,IDE會(huì)產(chǎn)生一條警告提醒你警惕無意識(shí)的覆蓋恭应。

  3. 總結(jié):如果你想要的每個(gè)方法聲明中使用Override注解來覆蓋超類聲明,編譯器就可以替你防止大量的錯(cuò)誤首启。但有一個(gè)例外:在具體的類中暮屡,不必標(biāo)注你確信覆蓋了抽象方法聲明的方法。

第三十七條毅桃、用標(biāo)記接口定義類型

  1. 標(biāo)記接口(Marker Interface)是沒有包含方法聲明的接口褒纲,只是指明(或者標(biāo)明)一個(gè)類實(shí)現(xiàn)了具有某種屬性的接口。例:Serializable接口钥飞,通過實(shí)現(xiàn)這個(gè)接口莺掠,類表明它的實(shí)例可以被寫到ObjectOutputStream(即“被序列化”)

  2. 標(biāo)記注解和標(biāo)記接口:

    • 標(biāo)記接口的優(yōu)點(diǎn):
      • 標(biāo)記接口定義的類型是由被標(biāo)記類的實(shí)例實(shí)現(xiàn)的;標(biāo)記注解則沒有定義這樣的類型读宙。這個(gè)類型允許你在編譯時(shí)捕捉在使用標(biāo)記注解的情況要到運(yùn)行時(shí)才能捕捉到的錯(cuò)誤彻秆。
      • 他們可以更加精確地進(jìn)行鎖定。
    • 標(biāo)記注解的優(yōu)點(diǎn):
      • 它可以通過默認(rèn)的方式添加一個(gè)或者多個(gè)注解類型元素,給已被使用的注解類型添加更多的信息唇兑。隨著時(shí)間的遷移酒朵,簡(jiǎn)單的標(biāo)記注解可以演變成更加豐富的注解類型,這在標(biāo)記接口中是不可能的;
      • 它們是更大的注解機(jī)制的一部分扎附。因此蔫耽,標(biāo)記注解在那些支持注解作為編程元素之一的框架中同樣具有一致性。
  3. 何時(shí)使用標(biāo)記注解和標(biāo)記接口留夜?

    如果標(biāo)記是應(yīng)用到任何程序元素而不是類或者接口匙铡,就必須使用注解,因?yàn)橹挥蓄惡徒涌梢杂脕韺?shí)現(xiàn)或者擴(kuò)展接口碍粥。如果標(biāo)記只應(yīng)用在類和接口鳖眼,思考下:我要編寫一個(gè)或者多個(gè)只接受有這種標(biāo)記的方法嗎?如果是這樣嚼摩,優(yōu)先使用標(biāo)記接口钦讳。如果不是,再思考下:是否要永遠(yuǎn)限制這個(gè)標(biāo)記只用于特殊接口的元素枕面?如果是蜂厅,則優(yōu)先使用標(biāo)記接口。 最后選擇使用標(biāo)記注解膊畴。

  4. 總結(jié):標(biāo)記接口和標(biāo)記注解都有用處,如何選擇見上病游。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末唇跨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子衬衬,更是在濱河造成了極大的恐慌买猖,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滋尉,死亡現(xiàn)場(chǎng)離奇詭異玉控,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)狮惜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門高诺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人碾篡,你說我怎么就攤上這事虱而。” “怎么了开泽?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵牡拇,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng)惠呼,這世上最難降的妖魔是什么导俘? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮剔蹋,結(jié)果婚禮上旅薄,老公的妹妹穿的比我還像新娘。我一直安慰自己滩租,他們只是感情好赋秀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著律想,像睡著了一般猎莲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上技即,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天著洼,我揣著相機(jī)與錄音,去河邊找鬼而叼。 笑死身笤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的葵陵。 我是一名探鬼主播液荸,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼脱篙!你這毒婦竟也來了娇钱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤绊困,失蹤者是張志新(化名)和其女友劉穎文搂,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秤朗,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡煤蹭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了取视。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片硝皂。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖作谭,靈堂內(nèi)的尸體忽然破棺而出吧彪,到底是詐尸還是另有隱情,我是刑警寧澤丢早,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布姨裸,位于F島的核電站秧倾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏傀缩。R本人自食惡果不足惜那先,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赡艰。 院中可真熱鬧售淡,春花似錦、人聲如沸慷垮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽料身。三九已至汤纸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芹血,已是汗流浹背贮泞。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留幔烛,地道東北人啃擦。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像饿悬,于是被迫代替她去往敵國和親令蛉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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

  • 枚舉類型是指由一組固定的常量組成合法值的類型狡恬,例如一年中的季節(jié)言询,太陽系中的行星或者一副牌中的花色。在編程語言...
    小小輝_710a閱讀 1,436評(píng)論 0 0
  • 經(jīng)典重讀——亞馬遜鏈接 筆記鏈接 導(dǎo)圖: 筆記文本: Effective Java1 第2章 創(chuàng)建和銷毀對(duì)象1.1...
    8c3c932b5ffd閱讀 1,424評(píng)論 0 1
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理傲宜,服務(wù)發(fā)現(xiàn),斷路器夫啊,智...
    卡卡羅2017閱讀 134,651評(píng)論 18 139
  • 對(duì)象的創(chuàng)建與銷毀 Item 1: 使用static工廠方法函卒,而不是構(gòu)造函數(shù)創(chuàng)建對(duì)象:僅僅是創(chuàng)建對(duì)象的方法,并非Fa...
    孫小磊閱讀 1,981評(píng)論 0 3
  • 人生最快樂的事不是高官厚祿的擔(dān)驚受怕,也不是舒適安逸的碌碌無為熊榛,更不是日復(fù)一日的枯燥生活锚国。 我認(rèn)為最快樂的事就是,...
    公子涼閱讀 2,719評(píng)論 8 13