美文网首页
[Effective Java] Item 30: Use en

[Effective Java] Item 30: Use en

作者: YoungJadeStone | 来源:发表于2019-05-05 05:26 被阅读0次

在Java 1.5中,有两个类型被加入:enum type和annotation type。

Java docs里对enum的定义如下:

An enum type is a special data type that enables for a variable to be a set of predefined constants. The variable must be equal to one of the values that have been predefined for it. Common examples include compass directions (values of NORTH, SOUTH, EAST, and WEST) and the days of the week.

不用Enum时

如果不用enum,我们常用 int enum pattern 来表示整形常数,比如:

// the int enum pattern - severely deficient!
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;

public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;

这样定义常量:

  1. 使用不方便
  2. 类型不安全。如果我们试图把一个apple常量传给属于orange的method,编译器不会报错。
  3. 编译器会用 == 将apple和orange进行比较。
  4. 更甚者,会出现下面的情况:
// tasty citrus flavored applesauce!
int i = (APPLE_FUJI - ORANGE_TEMPLE) / APPLE_PIPPIN;

改用Enum Type

public enum Apple { FUJI, PIPPIN, GRANNY_SMITH }
public enum Orange { NAVEL, TEMPLE, BLOOD }

enum type通过 public static final 域为每个枚举常量导出instance。enum type实际上是instance-controlled的,它们算是广义上的singleton。

enum type类型有一下几个优势:

  1. 类型安全
  2. 多个enum type类型可以在一个系统中和平共处,因为每个类型都有自己的命名空间。
  3. 当你更改enum type里定义的常量时,比如又加一个常量,不需要recompile client的代码,因为enum type没有被编译到client代码中,它们在int enum pattern里。
  4. 允许添加任意的method和field,实现任意的interface。它们提供了所有Object方法的高级实现,实现了Comparable和Serializable接口。

我们用个例子来看下:

// enum type with data and behavior
public enum Planet {
  MERCURY(3.3e+23, 2.4e6)
  VENUS(4.8e+24, 6.0e6)
  EARTH(5.9e+24, 6.3e6)
  MARS(6.1e+23, 3.3e6)
  ....

  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() {
    return mass * surfaceGravity; // F = ma
  }
}

下面的代码实际使用enum Planet

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.println("Weight on %s is %f%n", p, p.surfaceWeight(mass));
    }
  }
}

不同behavior关联不同常量

上面每个Planet常量都关联了不同的数据,但有时我们需要将本质上不同的behavior与每个常量关联起来。

简单但是不那么好的一种实现方式是:

// enum type that switches on its own value - questional
public enum Operation {
  PLUS, MINUS, TIMES, DIVIDE;

  // do the arithmetic op represented by this constant
  double apply(double x, double y) {
    switch() {
      case PLUS: return x + y;
      case MINUS: return x - y;
      case TIMES: return x * y;
      case DIVIDE: return x / y;
    }
    throw new AssertionError("Unknown op: " + this);
  }
}

这段代码并不好是因为如果我们添加了新的enum常量,但是忘记给switch添加对应的条件,编译仍然能通过,但是运行新的常量是会fail。

在enum type中有一种更好的方法:声明一个抽象的apply方法,并在 constant-specific class body 中,用具体的方法覆盖每个常量的抽象的apply方法。这种方法叫 constant-specific method implementation

// enum type with constant-specific method implementations
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; } };

  abstract double apply(double x, double y);
}

// enum type with constant-specific class bodies and data
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);
}

public Test {
  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.println("%f %s %f = %f%n", x, op, y, op.apply(x, y));
  }
}

多个常量同时共享相同行为

上面 constant-specific method implementations 有个缺点,就是不方便share代码。比如下面的计算工资的例子:

// enum that switches on its value to share code - questionable
enum PayrollDay {
  MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
  private static final int HOURS_PER_SHIFT = 8;
  double pay(double hoursWorked, double payRate) {
    double basePay = hoursWorked * payRate;
    double overtimePay; // calculate overtime pay
    switch(this) {
      case SATURDAY: case SUNDAY:
        overtimePay = hoursWorked * payRate / 2;
      default: // weekdays
        overtimePay = hoursWorked <= HOURS_PER_SHIFT ? 0 : (hoursWorked - HOURS_PER_SHIFT) * payRate / 2;
        break;
    }
    return basePay + overtimePay;
  }
}

上面的代码虽然能工作,但是很危险。如果我们加了一个常量(某种假期),但是忘了实现switch里面的方法,程序的behavior就会变(应该走假期的路径,但是走了default路径)。

strategy enum pattern 可以很好的解决这个问题,我们来直接看代码:

// the strategy enum pattern
enum PayrollDay {
  MONDAY(PayType.WEEKDAY), 
  THESDAY(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);
    }
  }
}

Enum Type缺点

与int enum pattern相比,它的装载和初始化枚举时会有时间和空间的成本。

相关文章

网友评论

      本文标题:[Effective Java] Item 30: Use en

      本文链接:https://www.haomeiwen.com/subject/odfenqtx.html