EffectiveJava第6章-枚舉和注解

第30條:用enum代替int常量

(1)int枚舉模式

public static final int APPLE_FUJI = 0;

比較脆弱瓶埋,如果與枚舉常量關(guān)聯(lián)的int發(fā)生了變化兼蕊,客戶端需重新編譯(編譯時常量)果正。另外沒有便利的方法將常量名稱打印出來秦躯。

(2)枚舉版本

public enum Apple {
   FUJI,PIPPIN,GRANNY_SMITH
}

Java的枚舉本質(zhì)是int值忆谓。

枚舉類型都是final的,因為沒有可以訪問的構(gòu)造器(private)□獬校客戶端既不能創(chuàng)建枚舉類型倡缠,也不能對它進(jìn)行擴(kuò)展。

Java枚舉類型繼承了java.lang.Enum茎活。

實例

public enum Planet {
    MERCURY(3.302e+23, 2.439e6),
    VENUS  (4.869e+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;           // In kilograms
    private final double radius;         // In meters
    private final double surfaceGravity; // In m / s^2

    // Universal gravitational constant in m^3 / kg s^2
    private static final double G = 6.67300E-11;

    // Constructor毡琉,不能加public,實際上編譯器會把其變成private妙色。通過查看字節(jié)碼桅滋,
    // 你還可以看到它的構(gòu)造函數(shù)中還有兩個默認(rèn)的參數(shù),int ordinal和String name
    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;  // F = ma
    }
}

(3)枚舉的行為

//提供了四個枚舉對象身辨,每一個枚舉對象提供(覆寫)了不同的行為方法丐谋。

public enum Operation {
    PLUS("+") {
        double apply(double x, double y) { return x + y; }
    },
    MINUS("-") {
        double apply(double x, double y) { return x - y; }
    },
    TIMES("*") {
        double apply(double x, double y) { return x * y; }
    },
    DIVIDE("/") {
        double apply(double x, double y) { return x / y; }
    };
    private final String symbol;
    Operation(String symbol) { this.symbol = symbol; }
    @Override public String toString() { return symbol; }

    abstract double apply(double x, double y);

    // Implementing a fromString method on an enum type - Page 154
    private static final Map<String, Operation> stringToEnum
        = new HashMap<String, Operation>();
        
    //final常量的創(chuàng)建要比靜態(tài)代碼塊要早
    static { // Initialize map from constant name to enum constant
        for (Operation op : values())
            stringToEnum.put(op.toString(), op);
    }
    // Returns Operation for string, or null if string is invalid
    public static Operation fromString(String symbol) {
        return stringToEnum.get(symbol);
    }


    // Test program to perform all operations on given operands
    public static void main(String[] args) {
        double x = Double.parseDouble(args[0]);
        double y = Double.parseDouble(args[1]);
        for (Operation op : Operation.values())
            System.out.printf("%f %s %f = %f%n",
                              x, op, y, op.apply(x, y));
                              
        //x和y的toString方法返回Enum中的name域
        //values方法按照聲明順序返回它的值數(shù)組
    }
}

(4)枚舉策略

// The strategy enum pattern
enum PayrollDay {
    MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY),
    WEDNESDAY(PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY),
    FRIDAY(PayType.WEEKDAY),
    SATURDAY(PayType.WEEKEND), SUNDAY(PayType.WEEKEND);

    private final PayType payType;
    PayrollDay(PayType payType) { this.payType = payType; }

    double pay(double hoursWorked, double payRate) {
        return payType.pay(hoursWorked, payRate);
    }
    // The strategy enum type
    private enum PayType {
        WEEKDAY {
            double overtimePay(double hours, double payRate) {
                return hours <= HOURS_PER_SHIFT ? 0 :
                  (hours - HOURS_PER_SHIFT) * payRate / 2;
            }
        },
        WEEKEND {
            double overtimePay(double hours, double payRate) {
                return hours * payRate / 2;
            }
        };
        private static final int HOURS_PER_SHIFT = 8;

        abstract double overtimePay(double hrs, double payRate);

        double pay(double hoursWorked, double payRate) {
            double basePay = hoursWorked * payRate;
            return basePay + overtimePay(hoursWorked, payRate);
        }
    }
}

第31條:用實例域代替序數(shù)

(1)ordinal方法

oridinal()

(2)永遠(yuǎn)不要根據(jù)枚舉的序數(shù)導(dǎo)出與它關(guān)聯(lián)的值

// Enum with integer data stored in an instance field
public enum Ensemble {
    SOLO(1), DUET(2), TRIO(3), QUARTET(4), QUINTET(5),
    SEXTET(6), SEPTET(7), OCTET(8), DOUBLE_QUARTET(8),
    NONET(9), DECTET(10), TRIPLE_QUARTET(12);

    private final int numberOfMusicians;
    Ensemble(int size) { this.numberOfMusicians = size; }
    public int numberOfMusicians() { return numberOfMusicians; }
}

采用單獨的屬性存儲,這樣維護(hù)起來方便煌珊,否則枚舉順序一改号俐,整個都得改。

第32條:用EnumSet代替位域

(1)位域

public class Text{
   public static final int STYLE_1 = 1 << 0; //1
   public static final int STYLE_2 = 1 << 1; //2
   public static final int STYLE_3 = 1 << 2; //4
   public static final int STYLE_4 = 1 << 3; //8
   
   public void applyStyles(int styles){
      ...
   }
}

text.applyStyles(STYLE_1 | STYLE_2);

//好處就是高效定庵,但是比較晦澀吏饿。
(2)EnumSet

public class Text {
    public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }

    // Any Set could be passed in, but EnumSet is clearly best
    public void applyStyles(Set<Style> styles) {
        // Body goes here
    }

    // Sample use
    public static void main(String[] args) {
        Text text = new Text();
        text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));
    }
}

第33條:用EnumMap代替序數(shù)索引

(1)不要使用ordinal()去做數(shù)據(jù)索引

public enum Phase {
   SOLID, LIQUID, GAS;

   public enum Transition {
      MELT, FREEZE,
      BOIL,   CONDENSE,
      SUBLIME, DEPOSIT;

      private final Phase src;
      private final Phase dst;
        
      Transition(Phase src, Phase dst) {
         this.src = src;
         this.dst = dst;
      }
      //don't do this
      private static final Transition[][] TRANSITIONS = {
         {null,MELT,SUBLIME},
         {FREEZE,null,BOIL},
         {DEPOSIT,CONDENSE,null}
      }
      
      //return
      public static Transition from(Phase src,Phase dst){
         return TRANSITIONS[src.oridinal()][dst.oridinal()];
      }
   }
}

(2)使用EnumMap

// Simplistic class representing a culinary herb - Page 161
public class Herb {
public enum Type { ANNUAL, PERENNIAL, BIENNIAL }

private final String name;
private final Type type;

Herb(String name, Type type) {
    this.name = name;
    this.type = type;
}

@Override public String toString() {
    return name;
}

public static void main(String[] args) {
   
    Herb[] garden = {
        new Herb("Basil",    Type.ANNUAL),
        new Herb("Carroway", Type.BIENNIAL),
        new Herb("Dill",     Type.ANNUAL),
        new Herb("Lavendar", Type.PERENNIAL),
        new Herb("Parsley",  Type.BIENNIAL),
        new Herb("Rosemary", Type.PERENNIAL)
    };

    // Using an EnumMap to associate data with an enum - Page 162
    Map<Herb.Type, Set<Herb>> herbsByType =
        new EnumMap<Herb.Type, Set<Herb>>(Herb.Type.class);
    for (Herb.Type t : Herb.Type.values())
        herbsByType.put(t, new HashSet<Herb>());
    for (Herb h : garden)
        herbsByType.get(h.type).add(h);
    System.out.println(herbsByType);
}

}

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

枚舉類型是final的踪危,所以無法被繼承。
枚舉類型可以實現(xiàn)接口猪落,從而進(jìn)行枚舉的擴(kuò)展贞远。

(1)使用接口進(jìn)行擴(kuò)展

public interface Operation {
    double apply(double x, double y);
}

//擴(kuò)展1
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;
    }
}

//擴(kuò)展2
public enum ExtendedOperation 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;
    ExtendedOperation(String symbol) {
        this.symbol = symbol;
    }
    @Override public String toString() {
        return symbol;
    }

    // Test class to exercise all operations in "extension enum" - Page 167
    public static void main(String[] args) {
        double x = Double.parseDouble(args[0]);
        double y = Double.parseDouble(args[1]);
        test(ExtendedOperation.class, x, y);

        System.out.println();  // Print a blank line between tests
        test2(Arrays.asList(ExtendedOperation.values()), x, y);
    }

    // 使用綁定類型 test parameter is a bounded type token  (Item 29)
    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));
    }

    // 使用有限通配符類型 test parameter is a bounded wildcard type (Item 28)
    private static void test2(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));
    }
}

6、注解優(yōu)于命名模式

(1)命名模式的缺點

A笨忌、文字拼寫容易出錯

B蓝仲、無法確保它們只用于相應(yīng)的程序元素上

C、沒有提供將參數(shù)值與程序元素關(guān)聯(lián)起來的好方法

(2)使用注解

// Program containing marker annotations - Page 170
public class Sample {
    @Test public static void m1() { }  // Test should pass
    public static void m2() { }
    @Test public static void m3() {    // Test Should fail
        throw new RuntimeException("Boom");
    }
    public static void m4() { }
    @Test public void m5() { } // INVALID USE: nonstatic method
    public static void m6() { }
    @Test public static void m7() {    // Test should fail
        throw new RuntimeException("Crash");
    }
    public static void m8() { }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}

異常注解實例

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
    Class<? extends Exception>[] value();
}


public class Sample2 {
    @ExceptionTest(ArithmeticException.class)
    public static void m1() {  // Test should pass
        int i = 0;
        i = i / i;
    }
    @ExceptionTest(ArithmeticException.class)
    public static void m2() {  // Should fail (wrong exception)
        int[] a = new int[0];
        int i = a[1];
    }
    @ExceptionTest(ArithmeticException.class)
    public static void m3() { }  // Should fail (no exception)

    // Code containing an annotation with an array parameter - Page 174
    @ExceptionTest({ IndexOutOfBoundsException.class,
                NullPointerException.class })
    public static void doublyBad() {
        List<String> list = new ArrayList<String>();

        // The spec permits this method to throw either
        // IndexOutOfBoundsException or NullPointerException
        list.addAll(5, null);
    }
}

第36條:堅持使用Override注解

(1)覆蓋誤寫成重載

public boolean equals(Bigram o){
  return b.first == o.first && b.second == second;
}

(2)使用Override注解

//參數(shù)必須是Object官疲,使用了注解能夠在編譯時檢查方法覆寫是否正確
@Override
public boolean equals(Object o){
   if(!(o instance of Bigram)){
      return false;
   }
   Bigram b = (Bigram)o;
   return b.first == first && b.second == second;
}

第37條:用標(biāo)記接口定義類型

(1)標(biāo)記接口

即沒有包含方法聲明的接口袱结,只是用來標(biāo)明一個類實現(xiàn)了具有某種屬性的接口,比如Serializable接口途凫,通過實現(xiàn)這個接口垢夹,類標(biāo)明它的實例可以被寫到ObjectOutputStream,即序列化

(2)實例

Set接口就是有限制的標(biāo)記接口维费,只適用于Collection的子類型果元,不會添加除了Collection定義之外的方法。

如果想要定義一個任何新方法都不會與之關(guān)聯(lián)的類型掩完,標(biāo)記接口就是最好的選擇噪漾。
如果想要標(biāo)記程序元素而非類和接口,考慮到未來可能要給標(biāo)記添加更多的信息且蓬,或者標(biāo)記要適合于已經(jīng)廣泛使用了注解類型的框架欣硼,那么標(biāo)記注解是正確的選擇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末恶阴,一起剝皮案震驚了整個濱河市诈胜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌冯事,老刑警劉巖焦匈,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異昵仅,居然都是意外死亡缓熟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進(jìn)店門摔笤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來够滑,“玉大人,你說我怎么就攤上這事吕世≌么ィ” “怎么了?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵命辖,是天一觀的道長况毅。 經(jīng)常有香客問我分蓖,道長,這世上最難降的妖魔是什么尔许? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任么鹤,我火速辦了婚禮,結(jié)果婚禮上母债,老公的妹妹穿的比我還像新娘午磁。我一直安慰自己尝抖,他們只是感情好毡们,可當(dāng)我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著昧辽,像睡著了一般衙熔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搅荞,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天红氯,我揣著相機(jī)與錄音,去河邊找鬼咕痛。 笑死痢甘,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的茉贡。 我是一名探鬼主播塞栅,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼腔丧!你這毒婦竟也來了放椰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤愉粤,失蹤者是張志新(化名)和其女友劉穎砾医,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體衣厘,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡如蚜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了影暴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片错邦。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖坤检,靈堂內(nèi)的尸體忽然破棺而出兴猩,到底是詐尸還是另有隱情,我是刑警寧澤早歇,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布倾芝,位于F島的核電站讨勤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏晨另。R本人自食惡果不足惜潭千,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望借尿。 院中可真熱鬧刨晴,春花似錦、人聲如沸路翻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茂契。三九已至蝶桶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間掉冶,已是汗流浹背真竖。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留厌小,地道東北人恢共。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像璧亚,于是被迫代替她去往敵國和親讨韭。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,974評論 2 355

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