第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)記注解是正確的選擇。