【06】Effective Java - 枚举和注解

1、用enum代替int常量

(1)int枚举模式
public static final int APPLE_FUJI = 0;

   比较脆弱,如果与枚举常量关联的int发生了变化,客户端需重新编译。另外没有便利的方法将常量名称打印出来。


(2)枚举版本
public enum Apple {
   FUJI,PIPPIN,GRANNY_SMITH
}

  实例

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

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


2、用实例域代替序数

(1)ordinal方法
oridinal()

(2)永远不要根据枚举的序数导出与它关联的值
// 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; }
}

  采用单独的属性存储,这样维护起来方便,否则枚举顺序一改,整个都得改。


3、用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));
    }
}


4、用EnumMap代替序数索引

(1)不要使用ordinal()去做数据索引
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);
    }
}


5、用接口模拟可伸缩的枚举

(1)使用接口进行扩展
public interface Operation {
    double apply(double x, double y);
}

//扩展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;
    }
}

//扩展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、注解优于命名模式

(1)命名模式的缺点

A、文字拼写容易出错

B、无法确保它们只用于相应的程序元素上

C、没有提供将参数值与程序元素关联起来的好方法


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


7、坚持使用Override注解

(1)覆盖误写成重载
public boolean equals(Bigram o){
  return b.first == o.first && b.second == second;
}

(2)使用Override注解
@Override
public boolean equals(Object o){
   if(!(o instance of Bigram)){
      return false;
   }
   Bigram b = (Bigram)o;
   return b.first == first && b.second == second;
}


8、用标记接口定义类型

(1)标记接口

  即没有包含方法声明的接口,只是用来标明一个类实现了具有某种属性的接口,比如Serializable接口,通过实现这个接口,类标明它的实例可以被写到ObjectOutputStream,即序列化


(2)实例

Set接口就是有限制的标记接口,只适用于Collection的子类型,不会添加除了Collection定义之外的方法。


你可能感兴趣的:(【06】Effective Java - 枚举和注解)