30.使用enum代替int常量
以前的方案
在枚舉出現前,都是 使用常量的方式,如
public static final int APPLE_FUJI = 0;
public static final int ORANGE_NAVEL = 0;
這種方稱爲枚舉常量
,其弊端有:
- 如果與枚舉常量關聯的
int
發生變化,則必須重新編譯 - 如果將枚舉常量翻譯成可打印的字符串,只能見到一個數字,沒有太大的用處.
枚舉方式
java 中的枚舉本質上是
int
值.
public enum Apple{FUJI}
public enum Apple{NAVEL}
- 枚舉類基本想法 : 通過公有的
final
域爲 每個枚舉常量 導出實例的類. - 因爲沒有可訪問的構造器, 因此枚舉類型都是
final
的 - 因爲
客戶端
無法創建 枚舉實例
,也不能對其進行擴展
,因此枚舉是 實例受控的,單例的泛型化
- 編譯時類型安全,聲明後
取值
一定是 枚舉中的有效值
之一 - 通過複寫
toString
,可以將枚舉
的值打印出來. - 枚舉可以添加任意的
方法和域
,並實現任意的接口
枚舉的高級用法
switch
,枚舉中可以通過switch(this)
來根據不同的域
做不同的操作,
示例 : Operation.java
當然,上面種方式並不好,建議使用
特定於常量的方法實現
(onstant-specific method implementation) Operation1.java可以通過構造函數傳遞參數,例如,示例:Operation.java,
op
打印是調用toString
打印出了+,-,*,/
;利用
策略枚舉
,可以用在更加安全,靈活的場景.如:書中的加班場景,每添加一種枚舉常量就強制添加一種策略
,示例代碼:PayrollDay.java
總結
switch
枚舉 適合於給外部的
枚舉類型增加特定於 常量
的行爲.- 一般來說,枚舉會 優先使用
comparable
類型.而非int
類型 - 需要一組固定常量的時候就可以使用枚舉.
- 枚舉
裝載和初始化的時候會有 空間和時間 的成本.
31.用實例域代替序數
簡介
枚舉的
ordinal()
方法會返回枚舉常量在類型中的數字位置,
但是儘量不要使用它,因爲當重新排序
後,會對客戶端造成破壞.
正確的做法是,將他保存在一個 實例域
中.
示例
public enum Ensemble {
SOLO(1), DUET(2), TRIO(3), QUARTET(4);
private final int numberOf;
Ensemble(int _i) {
this.numberOf = _i;
}
public int getNumberOf() {
return numberOf;
}
}
32.使用enumset代替位域
int 枚舉模式
public class Text {
public static final int STYLE_BOLD = 1 << 0;
public static final int STYLE_ITALIC = 1 << 1;
public static final int STYLE_UNDERLINE = 1 << 2;
public static final int STYLE_STRIKETHROUGH = 1 << 3;
public void applyStyles(int styles) {
//...
}
}
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
}
}
- EnumSet 實現了 Set 接口,提供了豐富的功能,類型安全.可以從其他任何
Set
中得到互換性.- 整個 EnumSet 就是用 單個 long 來表示的,
性能上比得上 位運算的性能.
- 總而言之因爲枚舉類型要用在
集合(Set)
中,所以沒有理由用位域
來表示.
33.用enummap代替序數索引
簡介
這裏的總體原則 和上一個 一致, 就是 儘量不要使用
ordinal()
方法.
以枚舉序數
作爲數組索引
總不是那麼精確.
建議使用 EmumMap
來索引數組,如果是 多維的 ,可以使用 Enum<...,Enum<?>>
書中 示例代碼: Herb.java
34.用接口模擬可伸縮的枚舉
簡介
- 如果讓一個
枚舉類型
去擴展另一個枚舉類型
,利用語言的特性,幾乎是不可能的/- 枚舉的可擴展性,到最後都證明不是一個好點子.
接口模擬枚舉的伸縮性
鑑於如上兩點,我們可以利用枚舉定義接口
和操作碼類型
來擴展枚舉,使枚舉具有可擴展性. 示例代碼: Operation.java
一.定義如下接口
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;
}
},
//...
}
三. 擴展實現類型
public enum ExtendedOperation implements Operation {
EXP("^") {
public double apply(double x, double y) {
return Math.pow(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));
}
確保了
Class
既是枚舉類型
,又是Operation
的子類型
- 雖然無法編寫可擴展的枚舉類型,卻可以通過接口來模擬其伸縮性.
- 這裏唯一不足,如上的
保存和獲取
與某項操作相關聯的符號的邏輯代碼,仍無法複用,最好建立輔助類helper
來操作.
35.註解優先於命名模式
簡介
在1.5
之前,Java
使用的 是命名模式
,如JUnit
,這種模式有以下缺點:
- 拼寫錯誤不能及時發現
- 無法保證
命名
只在正確的場景使用 - 沒有
值/類型信息
,編譯器無法提前發現問題
使用 註解
可以很好的解決 如上問題,通過元註解進行約定
@Retention
: 限定保留時期@Target
: 限定其應用的程序元素- 還有很多註解,如
@IntDef
,@ViewDebug
… - 註解接收的參數如果是數組,爲其賦值一個單獨的元素也是合法的,如下
@ExceptionTest({
IndexOutOfBoundsException.class,NullPointerException.class
})
36.堅持使用override屬性
簡介
@override 註解表明,被註解的
方法聲明
覆蓋了超類中的方法聲明.
如果使用此註解,而不是超類
中的方法聲明,就會報編譯時錯誤
所以,最好是在 每個想要覆蓋超類的方法
聲明上添加 @override
註解
37.用標記接口定義類型
簡介
標記接口就是
不包含方法聲明
的接口.指明一個實現類 具有 某種屬性
如Serializable
表明實現類 可以被寫到ObjectOutputStream
中
在 第 35item
中瞭解過 標記註解
,在這裏所說的是標記接口
.
標記接口
相當於 標記註解
有如下特點
- 標記接口定義的
類型
是由被標記的類的實例
實現的,標記註解
則沒有定義這樣的類型 - 他們 可以被 更加精確的進行鎖定,比如
如果註解類型利用
@Target(ElementType.TYPE)
標記,則它可以被應用到任何類或者接口上
標記註解
勝過 標記接口
的地方
- 它可以通過
默認的方式
添加一個或者多個註解類型元素
,給已被使用的註解
類型添加更多的信息.隨着時間的推移,簡單的標記註解類型
可以演變成更加豐富的註解類型.
- 他們是更大的註解機制的一部分.
何時使用註解
,何時使用接口
- 如果
標記
是應用到任何程序元素
而不是類或者接口
,就必須使用註解.
因爲只有類和接口可以用來實現或者擴展接口
- 如果
標記
只應用給類和接口
,就應該優先使用標記接口而非註解
總而言之
- 如果想要定義 一個任何新方法都不會與之關聯的類型,標記接口是最好的選擇
- 如果想要標記程序元素而非類和接口,考慮到未來可能要給標記添加更多信息,標記註解是更好的選擇.