EffectiveJava--枚舉和註解

[b]本章內容:[/b]
1. 用enum代替int常量
2. 用實例域代替序數
3. 用EnumSet代替位域
4. 用EnumMap代替充數索引
5. 用接口模擬可伸縮的枚舉
6. 註解優先於命名模式
7. 堅持使用Override註解
8. 用標記接口定義類型

[b]1. 用enum代替int常量[/b]
枚舉類型是指由一組固定的常量組成合法值的類型,該特徵是在Java 1.5 中開始被支持的,之前的Java代碼都是通過“公有靜態常量域字段”的方法來簡單模擬枚舉的,如:
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;
... ...
public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;
這樣的寫法是比較脆弱的。首先是沒有提供相應的類型安全性,如兩個邏輯上不相關的常量值之間可以進行比較或運算(APPLE_FUJI - ORANGE_TEMPLE)。再有就是常量int是編譯時常量,被直接編譯到使用他們的客戶端中。如果與該常量關聯的int發生了變化,客戶端就必須重新編譯。如果沒有重新編譯,程序還是可以執行,但是他們的行爲將不確定。
下面我們來看一下Java 1.5 中提供的枚舉的聲明方式:
public enum Apple { FUJI, PIPPIN, GRANNY_SMITH }
public enum Orange { NAVEL, TEMPLE, BLOOD }
Java的枚舉本質上是int值。Java枚舉類型背後的基本想法非常簡單,就是通過公有的靜態final域爲每個枚舉常量導出實例的類,因爲沒有構造器,枚舉類型是真正的final。因爲客戶端既不能創建枚舉類型的實例,也不能對它進行擴展。換句話說,枚舉類型是實例受控的,它們是的泛型化,本質上是單元素的枚舉。
和“公有靜態常量域字段”不同的是,如果函數的參數是枚舉類型,如Apple,那麼他的實際值只能來自於該枚舉所聲明的枚舉值,即FUJI, PIPPIN, GRANNY_SMITH,且一定是三個中的一個。如果試圖將Apple和Orange中的枚舉值進行比較,將會導致編譯錯誤。

你可以增加或者重新排列枚舉類型中的常量,而無需重新編譯它的客戶端代碼,因爲導出學時的域在枚舉類型和它的客戶端之間提供了一個隔離層:常量值並沒有被編譯到客戶端代碼中,而是在int枚舉模式中。
和C/C++中提供的枚舉不同的是,Java中允許在枚舉中添加任意的方法和域,並實現任意的接口。它們提供了所有Object方法的高級實現,實現了Comparable和Serializable接口,並針對權舉類型的可任意改變設計了序列化方式。下面先給出一個帶有域方法和域字段的枚舉聲明:
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; //千克
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;
}
}
在上面的枚舉示例代碼中,已經將數據和枚舉常量關聯起來了,因此需要聲明實例域字段,同時編寫一個帶有數據並將數據保存在域中的構造器。枚舉天生就是不可變的,因此所有的域字段都應該爲final的,並最好將它們做成私有的並提供公有的訪問方法。下面看一下該枚舉的應用示例:
public class WeightTable {
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.printf("Weight on %s is %f%n",p,p.surfaceWeight(mass));
}
}
程序輸出:
Weight on MERCURY is 66.133672
Weight on VENUS is 158.383926
Weight on EARTH is 175.000000
Weight on MARS is 66.430699
Weight on JUPITER is 442.693902
Weight on SATURN is 186.464970
Weight on URANUS is 158.349709
Weight on NEPTUNE is 198.846116
枚舉的靜態方法values()將按照聲明順序返回他的值數組。枚舉的toString方法返回每個枚舉值的聲明名稱。就像其它類一樣,除非迫不得已要將枚舉方法導出至它的客戶端,否則都應該將它聲明爲私有的,如有必要,則聲明爲包級私有有。

在實際的編程中,我們常常需要針對不同的枚舉常量提供不同的數據行爲,見如下代碼:
public enum Operation {
PLUS,MINUS,TIMES,pIDE;
double apply(double x,double y) {
switch (this) {
case PLUS: return x + y;
case MINUS: return x - y;
case TIMES: return x * y;
case pIDE: return x / y;
}
throw new AssertionError("Unknown op: " + this);
}
}
上面的代碼已經表達出這種根據不同的枚舉值,執行不同的操作。如果沒有throw語句它就不能進行編譯。但是上面的代碼在設計方面確實存在一定的缺陷,或者說漏洞,如果我們新增枚舉值的時候,所有和apply類似的域函數,都需要進行相應的修改,如有遺漏將會導致異常的拋出。
幸運的是,Java的枚舉提供了一種更好的方法可以將不同的行爲與每個枚舉常量關聯起來:在枚舉類型中聲明一個抽象的apply方法,並在特定於常量的類主體中,用具體的方法覆蓋每個常量的抽象apply方法,這種方法稱作特定於常量的方法實現 。如下:
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;} },
pIDE { double apply(double x,double y) { return x / y;} };
abstract double apply(double x, double y);
}
這樣在添加新枚舉常量時就不會輕易忘記提供相應的apply方法了,因爲該方法就緊跟在每個常量之後,即使你真的忘了,編譯器也會提醒你,因爲枚舉中的抽象方法必須被它所有常量中的具體方法所覆蓋。我們在進一步看一下如何將枚舉常量和特定的數據進行關聯,如下覆蓋toString方法示例:
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;} },
pIDE("/") { 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);
}
下面給出以上代碼的應用示例:
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));
}
輸出如下:
2.000000 + 4.000000 = 6.000000
2.000000 - 4.000000 = -2.000000
2.000000 * 4.000000 = 8.000000
2.000000 / 4.000000 = 0.500000

枚舉類型有一個自動產生的valueOf(String)方法,他將常量的名字轉變爲枚舉常量本身,如果在枚舉中覆蓋了toString方法(如上例),就需要考慮編寫一個fromString方法,將定製的字符串表示法變回相應的枚舉,見如下代碼:
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;} },
pIDE("/") { 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);
// 新增代碼
private static final Map<String,Operation> stringToEnum = new HashMap<String,Operation>();
static {
for (Operation op : values())
stringToEnum.put(op.toString(),op);
}
public static Operation fromString(String symbol) {
return stringToEnum.get(symbol);
}
}
需要注意的是,我們無法在枚舉常量構造的時候將自身放入到Map中。與此同時,枚舉構造器不可以訪問枚舉的靜態域,除了編譯時的常量域之外。

特定於常量的方法有一個美中不足的地方,它們使得在枚舉常量中共享代碼變得更加困難了。爲枚舉常量添加一個公有的行爲方法時,如果添加了一個新的元素到枚舉,但是忘記給switch語句添加相應的case,這是非常危險的。幸運的時,有一種很好的方法可以實現這一點,將這種行爲方法移到一個私有的嵌套枚舉中,稱之爲策略枚舉。如下:
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);
}
}
}
如上,如果多個枚舉常量同時共享相同的行爲,則考慮策略枚舉。

[b]2. 用實例域代替序數[/b]
Java中的枚舉提供了ordinal()方法,它返回每個枚舉常量在類型中的數字位置。你可以試着從序數中得到關聯的int值:
public enum Color {
WHITE,RED,GREEN,BLUE,ORANGE,BLACK;
public int indexOfColor() {
return ordinal() + 1;
}
}
上面的枚舉中提供了一個獲取顏色索引的方法(indexOfColor),該方法將返回顏色值在枚舉類型中的聲明位置,如果我們的外部程序依賴了該順序值,那麼這將會是非常危險和脆弱的,因爲一旦這些枚舉值的位置出現變化,或者在已有枚舉值的中間加入新的枚舉值時,都將導致該索引值的變化。該條目推薦使用實例域的方式來代替枚舉提供的序數值,見如下修改後的代碼:
public enum Color {
WHITE(1),RED(2),GREEN(3),ORANGE(4),BLACK(5);
private final int indexOfColor;
Color(int index) {
this.indexOfColor = index;
}
public int indexOfColor() {
return indexOfColor;
}
}
Enum規範中談到ordinal時這麼寫道:“大多數程序員都不需要這個方法。它是設計成用於像EnumSet和EnumMap這種基於枚舉的通用數據結構的。”除非你在編寫的是這種數據結構,否則最好避免使用ordinal()方法。

[b]3. 用EnumSet代替位域[/b]
如果一個枚舉類型的元素主要用在集合中,一般就使用int枚舉模式,將2的不同倍數賦予每個常量:
下面的代碼給出了位域的實現方式:
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) { ... }
}
這種表示法讓你用OR位運算將幾個常量合併到一個集合中,稱爲位域。使用方式如下:
text.applyStyles(Text.STYLE_BOLD | Text.STYLE_ITALIC);、
位域表示法也允許利用位操作,有效地執行像union和intersection這樣的集合操作。但位域有着int枚舉常量的所有缺點,甚至更多。當位域以數字形式打印裏,翻譯位域比翻譯簡單的int枚舉常量要困難得多。

Java.util包提供了EnumSet類來有效地表示從單個枚舉類型中提取的多個值的多個集合,該類實現了Set接口,同時也提供了豐富的功能,類型安全性,以及可以從任何其他Set實現中得到的互用性。但是在內部具體實現上,每個EnumSet內容都表示爲位矢量。如果底層的枚舉類型有64個或者更少的元素,整個EnumSet就用單個long來表示,因此他的性能也是可以比肩位域的。與此同時,他提供了大量的操作方法,其實現也是基於位操作的,但是相比於手工位操作,由於EnumSet替我們承擔了這部分的開發,從而也避免了一些容易出現的低級錯誤,代碼的美觀程度也會有所提升,見如下修改的代碼:
public class Text {
public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }
public void applyStyles(Set<Style> styles) { ... }
}
applyStyles方法採用的是Set<Style>而非EnumSet<Style>。雖然看起來好像所有的客戶端都可以將EnumSet傳到這個方法,但是最好還是接受接口類型而非接受實現類型。
EnumSet提供了豐富的靜態工廠來輕鬆創建集合,如下:
public class TestEnmu {
public static void main(String[] args) {
// 創建一個指定類型的空的集合
EnumSet set = EnumSet.noneOf(MyEnum.class);
set.add(MyEnum.RED);
set.add(MyEnum.GREEN);
set.add(MyEnum.BULE);
showSet(set);
// 創建一個指定類型的所有數據的集合
EnumSet set2 = EnumSet.allOf(MyEnum.class);
showSet(set2);
// 創建指定類型指定初始數據的集合
EnumSet<MyEnum> set3 = EnumSet.of(MyEnum.GREEN, MyEnum.RED, MyEnum.WHITE);
showSet(set3);
// 創建指定類型,指定範圍的集合
// 包含邊界數據
EnumSet<MyEnum> set4 = EnumSet.range(MyEnum.RED, MyEnum.YELLOW);
showSet(set4);
// 集合的用法和普通的沒有區別
}
private static void showSet(Set set) {
System.out.println(Arrays.toString(set.toArray()));
}
}
enum MyEnum {
BLACK, WHITE, RED, BULE, GREEN, YELLOW
}

總而言之,正是因爲枚舉類型要用在集合中,所有沒有理由用位域來表示它。

[b]4. 用EnumMap代替序數索引[/b]
前面的條目已經給出了儘量不要直接使用枚舉的ordinal()方法的原因,這裏就不在做過多的贅述了。在這個條目中,只是再一次給出了ordinal()的典型用法,與此同時也再一次提供了一個更爲合理的解決方案用於替換ordinal()方法,從而進一步證明我們在編碼過程中應該儘可能減少對枚舉中ordinal()函數的依賴。見如下代碼表示一種烹飪用的香草:
public class Herb {
public enum Type { ANNUAL, PERENNIAL, BIENNIAL }
private final String name;
private final Type type;
Herb(String name, Type t1ype) {
this.name = name;
this.type = type;
}
@Override public String toString() {
return name;
}
}
現在假設有一個香草的數組,表示一座花園中的植物,它們被分成3類,分別是一年生(ANNUAL)、多年生(PERENNIAL)和兩年生(BIENNIAL),正好對應着Herb.Type中的枚舉值。現在我們需要做的是遍歷花園中的每一個植物,並將這些植物分爲3類,最後再將分類後的植物分類打印出來。

public static void main(String[] args) {
Herb[] garden = getAllHerbsFromGarden();
Set<Herb>[] herbsByType = (Set<Herb>[])new Set[Herb.Type.values().length];
for (int i = 0; i < herbsByType.length; ++i) {
herbsByType[i] = new HashSet<Herb>();
}
for (Herb h : garden) {
herbsByType[h.type.ordinal()].add(h);
}
for (int i = 0; i < herbsByType.length; ++i) {
System.out.printf("%s: %s%n",Herb.Type.values()[i],herbByType[i]);
}
}
這種方法確實可行,但是隱藏着許多問題。因爲數組不能與泛型兼容,程序需要進行未受檢的轉換,並且不能正確無誤地進行編譯。因爲數組不知道它的索引代表着什麼,你必須手工標註這些索引的輸出。但是這種方法最嚴重的問題在於,當你訪問一個按照枚舉的序數進行索引的數組時,使用正確的int值就是你的職責了,int不能提供枚舉的類型安全。你如果使用了錯誤的值,程序就會悄悄地完成錯誤的工作,或者幸運的話,會拋出ArrayIndexOutBoundException異常。
幸運的是,有一種更好的方法可以達到同樣的效果,數組實際上充當着從枚舉到值的映射,因此可能還要用到Map。更具體地說,有一種非常快速的Map實現專門用於枚舉鍵,稱作java.util.EnumMap。如下:
public static void main(String[] args) {
Herb[] garden = getAllHerbsFromGarden();
Map<Herb.Type,Set<Herb>> herbsByType = new EnumMap<Herb.Type,Set<Herb>>(Herb.Type.class);
for (Herb.Type t : Herb.Type.values()) {
herbssByType.put(t,new HashSet<Herb>());
}
for (Herb h : garden) {
herbsByType.get(h.type).add(h);
}
System.out.println(herbsByType);
}
和之前的代碼相比,這段代碼更加簡短、清楚,也更加安全,運行效率方面也是可以與使用ordinal()的方式想媲美的。它沒有不安全的轉換,不必手工標註這些索引的輸出,因爲映射鍵知道如何將自身翻譯成可打印字符串的枚舉,計算數組索引裏也不可能出錯。
注意EnumMap構造器採用鍵類型的Class對象,這是一個限制的類型令牌,它提供了運行裏的泛型信息。
總而言之,最好不要用序數來索引數組,而要使用EnumMap。如果你所表示的這種關係是多維的,就使用EnumMap<... , Enum<...>>。

[b]5. 用接口模擬可伸縮的枚舉[/b]
枚舉是無法被擴展(extends)的,這是一個無法迴避的事實。如果我們的操作中存在一些基礎操作,如計算器中的基本運算類型(加減乘除)。然而對於有些用戶來講,他們也可以使用更高級的操作,如求冪和求餘等。針對這樣的需求,該條目提出了一種非常巧妙的設計方案,即利用枚舉可以實現接口這一事實,我們將API的參數定義爲該接口,而不是具體的枚舉類型,見如下代碼:
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; }
}
pIDE("/") {
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;
}
}

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;
}
}

在可以使用基礎操作的任何地方,都可以使用新的操作,只要API是被寫成採用接口類型而非實現。通過以上的代碼可以看出,在任何可以使用BasicOperation的地方,我們也同樣可以使用ExtendedOperation,只要我們的API是基於Operation接口的,而非BasicOperation或ExtendedOperation。下面爲以上代碼的應用示例:
public static void main(String[] args) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
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));
}
}
注意,參數Class<T> opSet將推演出類型參數的實際類型,即上例中的ExtendedOperation。與此同時,test函數的參數類型限定確保了類型參數既是枚舉類型又是Operation的實現類,這正是遍歷元素和執行每個元素相關聯的操作所必須的。

還有一種方法是使用Collection<? Extends Operation>,這是個有限制的通配符類型,作爲opSet參數的類型:
public static void main(String[] args) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
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));
}
}
上面的代碼允許調用者將多個實現類型的操作合併到一起,另一方面,也放棄了在指定操作上使用EnumSet和EnumMap的功能,因此除非需要靈活地合併多個實現類型的操作,否則可能最好使用有限制的類型令牌。
用接口模擬可伸縮枚舉有個小小的不足,即無法將實現從一個枚舉類型繼承到另一個枚舉類型。

[b]6. 註解優先於命名模式[/b]
Java1.5發行版本之前,一般使用命名模式表明有些程序元素需要通過某種工具或者框架進行特殊處理。如JUnit測試框架原本要求它的用戶一定要用test作爲測試方法名稱的開頭。這種方法有幾個很嚴重的缺點如下:
(1)文字拼寫錯誤會導致失敗,且沒有任何提示。
(2)無法確保它們只用於相應的程序元素上。
(3)它們沒有提供將參數值與程序元素關聯起來的好方法。

註解很好地解決了上面所有這些問題,
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test{
@Test public void m1(){}
}
@Retention元註解表明,Test註解應該在運行裏保留,如果沒有保留,測試工具就無法知道Test註解。
@Target元註解表明,Test註解只在方法中才是合法的,它不能運用到類聲明、域聲明或者其他程序元素上。
@Test註解稱作標記註解,因爲它沒有參數,只是標註被註解的元素。
測試運行工具在命令行上使用完全匹配的類名,並通過調用Method.invoke反射式地運行類中所有標註了Test的方法。isAnnotationPresent方法告知該工具要運行哪些方法。如果測試拋出異常,反射機制就會將它封裝在InvocationTargetException中。該工具捕捉到了這個異常,並打印失敗報告,包含測試方法拋出的原始異常,這些信息是通過getCause方法從InvocationTargetException中提取出來的。
如果嘗試通過反射調用測試方法裏拋出InvocationTargetException之外的任何異常,表明編譯裏沒有捕捉到Test註解的無效用法。

總結,除了“工具鐵匠”之外,大多數程序員都不必定義註解類型。但是所有的程序員都應該使用Java平臺所提供的預定義的註解類型。還要考慮使用IDE或都靜態分析工具所提供的任何註解。這種註解可以提升由這些工具所提供的診斷信息的質量。但是要注意這些註解還沒有標準化,因此如果變換工具或者形成標準,就有很多工作要做了。

[b]7. 堅持使用Override註解[/b]
Override註解只能用在方法聲明中,表示被註解的方法聲明覆蓋了超類型中的一個聲明。
首先,如果沒有加Override註解,覆蓋很容易被誤寫成重載,如下:

public boolean equals(Bigram o){
return b.first == o.first && b.second == second;
}
如果是爲了覆蓋Object.equals,必須定義一個參數爲Object類型的equals方法。否則實例將使用從父類繼承的equals方法。完整使用註解如下:
@Override
public boolean equals(Object o){
if(!(o instance of Bigram)){
return false;
}
Bigram b = (Bigram)o;
return b.first == first && b.second == second;
}
現在的IDE提供了堅持使用Override註解的另一理由,這種IDE具有自動檢查功能,稱作代碼檢驗。如果啓用相應的代碼檢驗功能,當有一個方法沒有Override註解,卻覆蓋了超類方法時,IDE就會產生一條警告。
總之,如果在你想要的每個方法聲明中使用Override註解來覆蓋超類聲明,編譯器就可以替你防止大量的錯誤,但有一個例外,在具體的類中,不必標註你確信覆蓋了抽象方法聲明的方法(雖然這麼做也沒有什麼壞處)。

[b]8. 用標記接口定義類型[/b]
標記接口是沒有包含方法聲明的接口,而只是指明一個類實現了具有某種屬性的接口。如Serializable接口只是表明它的實例可以被寫到ObjectOutputStream,或者說被序列化。
你可能聽說過標記註解使得標記接口過時了。這種斷言是不正確的。標記接口有兩點用過標記註解,首先,也是最重要的一點是,標記接口定義的類型是由被標記類的實例實現的,標記註解則沒有定義這樣的類型。另一個優點是它們可以被更加精確地進行鎖定。
總之,標記接口和標記註解都用處,如果想要定義一個任何新方法都不會與之關聯的類型,標記接口就是最好的選擇,如果想要定義類型一定要使用接口。如果想要標記程序元素而非類和接口,考慮到未來可能要給標記添加更多的信息,或者標記要適合於已經廣泛使用了註解類型的框架,那麼標記註解就是正確的選擇。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章