EffectiveJava第6章-枚举和注解

第30条:用enum代替int常量

(1)int枚举模式

public static final int APPLE_FUJI = 0;

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

(2)枚举版本

public enum Apple {
   FUJI,PIPPIN,GRANNY_SMITH
}

Java的枚举本质是int值。

枚举类型都是final的,因为没有可以访问的构造器(private)。客户端既不能创建枚举类型,也不能对它进行扩展。

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。通过查看字节码,
    // 你还可以看到它的构造函数中还有两个默认的参数,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 stringToEnum
        = new HashMap();
        
    //final常量的创建要比静态代码块要早
    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方法按照声明顺序返回它的值数组
    }
}

(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条:用实例域代替序数

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

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

第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