疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

Java学习笔记枚举


 

   

  于Java 1.5增加的enum type...

  enum type是由一组固定的常量组成的类型,比如四个季节、扑克花色。

  在出现enum type之前,通常用一组int常量表示枚举类型。

  比如这样:

  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;

  如果只是想用作枚举,感觉这样也没什么。

  但如果把上面的苹果和橘子互作比较,或者写成....

  int i = (APPLE_FUJI - ORANGE_TEMPLE) / APPLE_PIPPIN;

  虽合法但诧异,这是在做果汁吗?

  而且,这种常量是compile-time常量,编译后一切都结束了,使用这个常量的地方都被替换为该常量的值。

  如果该常量值需要改变,所有使用该常量的代码都必须重新编译。

  更糟糕的情况是,不重新编译也可以正常运行,只不过会得到无法预测的结果。

  (ps:我觉得更遭的是有人直接把常量值写到代码里...)

  另外,比如上面的APPLE_FUJI,我想打印它的名字,不是它的值。

  不仅如此,我还想打印所有苹果,我想打印苹果一共有多少种类。

  当然,如果想打印也可以,只是相比直接使用enum,无论怎么做都很麻烦。

  如果使用enum,比如:

  public enum Apple { FUJI, PIPPIN, GRANNY_SMITH }

  public enum Orange { NAVEL, TEMPLE, BLOOD }

  看起来就是一堆常量,但是enum没有实例,也没有可访问的构造器,无法对其进行扩展。

  enum本身就是final,所以很多时候也直接用enum实现singleton。

  enum在编译时是类型安全的,比如有地方声明了上面代码中的Apple类型的参数,那么被传到该参数的引用肯定是三种苹果之一。

  而且enum本身就是一个类型,可以有自己的方法和field,而且可以实现接口。

  附上书中太阳系enum,很难想象如果有类似需求时用普通常量来实现。

  也许我可以声明一个Planet类,再给它加上field的方法,然后在一个constant类中声明为final 但这样却无法保证Planet类仅用作常量,所以还是用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

  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

  }

  }

  然后我们就可以这样使用Planet enum,无论是值还是名字,使用起来都很自然:

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

  }

  }

  其实像Planet这样的方式对多数使用枚举的场景而言足够了。

  也就是说每个Planet常量表达的是不同的数据,但也有例外。

  比如,我们要为enum中的每一个常量赋予不同的行为。

  下面是书中用enum表达计算的例子:

  import java.util.HashMap;

  import java.util.Map;

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

  private static final Map

  static {

  for (Operation op : values())

  stringToEnum.put(op.toString(), op);

  }

  public static Operation fromString(String symbol) {

  return stringToEnum.get(symbol);

  }

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

  }

  }

  对不同的枚举常量进行switch..case..其实也能表达出我们想要的效果。

  如果以后增加了新的常量则需要再对应加上一个case,当然,不加也不会有任何提示,然后最坏的情况就是运行时出了问题。

  如上面的代码是常量行为的正确使用方法,即constant-specific method implementation。

  为行为提供一个抽象,并为每一个常量提供一个实现,即一个枚举常量也是constant-specific class body。

  采用这种方式时,如果新增一个常量,则必须提供一个方法实现,否则编译器会给出提示,这就多了一层保障。

  遗憾的是,这种方式也有缺陷。

  比如我们有这样一个需求,计算某一天的薪水,这个某一天可以是一周中的某一天,也可能是某个节日,比如周一到周五使用相同的运算方式,周末另算,某节日另算。

  也就是说我需要在枚举中声明代表周一到周日的常量,如果我继续使用之前的方式去声明一个抽象方法,如果周一到周五采用完全一样的计算,则会出现五段完全相同的代码。

  但即使这样我们也不能用回switch..case..方式,增加一个常量时强制选择其选择一种行为实现是必须的。

  于是我们有一种叫strategy enum的方式,即枚举中声明另外一个枚举的field,该field则代表策略,并提供策略相关的行为。

  下面是书中代码:

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

  }

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

  }

  }

  }