【笔记34】用enum代替int常量

在java1.5之前,表示枚举类型的常用模式是声明一组具名的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;

缺点: 
1. 在类型安全方面,将apple传到想要orange的方法中,编译器并不能检测出错误; 
2. 因为int常量是编译时常量,被编译到使用它们的客户端中。若与枚举常量关联的int发生了变化,客户端需重新编译,否则它们的行为就不确定; 
3. 没有便利方法将int常量翻译成可打印的字符串。这里的意思应该是比如你想调用的是ORANGE_NAVEL,debug的时候显示的是0,但你不能确定是APPLE_FUJI还是ORANGE_NAVEL。

枚举类型是int枚举常量的替代解决方案:

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

Java枚举类型背后的基本想法非常简单:通过公有的静态final域为每个枚举常量导出实例的类。因为没有可以访问的构造器,枚举类型是真正的final。客户端既不能创建枚举类型的实例,也不能对它进行扩展,因此很可能没有实例,而只是声明过的枚举常量。换句话说,枚举类型是实例受控的。他们是单例(Singleton)的泛型化,本质上是单元素的枚举。枚举类型为类型安全的枚举(typesafe enum)模式。

枚举提供编译时的类型安全,如果一个参数的类型是Apple,就可以保证,被传入到该参数上的任何非null对象引用一定是FUJI,PIPPIN,GRANNY_SMITH三个之一。

包含同名常量的多个枚举类型可以共存,因为每个类型有自己的命名空间,增加或重新排列枚举类型的常量,无需重新编译客户端代码。

通过调用toString方法,可以将枚举转换成可打印的字符串。

除了完善了int枚举模式的不足之处,枚举类型还允许添加任意的方法和域,并实现任意的接口。他们提供了所有Object方法的高级实现,实现了Comparable和Serializable接口,并针对枚举类型的可任意改变性设计了序列化方式。

why枚举类型?

1、enum枚举常量与数据关联

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

public class PlanetDemo {
    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));
        }

        //args[0]=30输出结果
        //Weight on MERCURY is 11.337201
        //Weight on VENUS is 27.151530
        //Weight on EARTH is 30.000000
        //Weight on MARS is 11.388120
        //Weight on JUPITER is 75.890383
        //Weight on SATURN is 31.965423
        //Weight on URANUS is 27.145664
        //Weight on NEPTUNE is 34.087906
    }
}

2、枚举常量与行为关联

有些时候将enum枚举常量与数据关联还不够,还需要将枚举常量与行为关联。如采用枚举来写加、减、乘、除的运算。可以避免增加新枚举时,遗漏相应条件。代码如下:

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

特定于常量的方法实现可以与特定于常量的数据结合起来。覆盖toString使得打印算术表达式变得非常容易:

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 String symbol;
  Operation(String symbol) {this.symbol = symbol;}

  @Override public String toString() {return symbol;}

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

特定于常量的方法使得在枚举常量中共享代码变得困难。即如果每个枚举常量都有公共的部分处理该怎么办,如果每个枚举常量关联的方法里都有公共的部分,那不仅不美观,还违反了DRY原则。我们真正想要的是每当添加一个枚举常量时,就强制选择一种策略,这就是下面的枚举策略模式。

public enum PayRoll {
  MONDY(PayType.WEEKDAY),
  TUESDAY(PayType.WEEKDAY),
  WEDNESDAY(PayType.WEEKDAY),
  THURSDAY(PayType.WEEKDAY),
  FRIDAY(PayType.WEEKDAY),
  SATURDAY(PayType.WEEKEND),
  SUNDAY(PayType.WEEKEND);

  private final PayType payType;
  PayRoll(PayType payType) {
    this.payType = payType;
  }

  double pay(double hoursWorked, double payRate) {
    return payType.pay(hoursWorked, payRate);
  }

  private enum PayType {
    WEEKDAY {
      @Override
      double overtimePay(double hoursWorked, double payRate) {
        double overtime = hoursWorked - HOURS_PER_SHIFT;
        return overtime <= 0 ? 0 : overtime * payRate / 2;
      }
    },

    WEEKEND {
      @Override
      double overtimePay(double hoursWorked, double payRate) {
        return hoursWorked * payRate / 2;
      }
    };

    private static final int HOURS_PER_SHIFT = 8;
    abstract double overtimePay(double hoursWorked, double payRate);

    double pay(double hoursWorked, double payRate) {
      double basePay = hoursWorked * payRate;
      return basePay + overtimePay(hoursWorked, payRate);
    }
  }
}

虽然这种模式没有switch语句那么简洁,但更加安全,也更加灵活。如果枚举中的switch语句不是在枚举中实现特定于常量的行为的一种很好地选择,那么他们还有什么用处呢?枚举中的switch语句适合于给外部的枚举类型增加特定于常量的行为。

一般来说,枚举会优先使用comparable而非int常量。与int常量相比,枚举有个小小的性能缺点,即装载和初始化枚举时会有空间和时间的成本。除了受资源约束的设备,例如手机和烤面包机之外,在实践中不必太在意这个问题。

什么时候应该使用枚举呢?

每当需要一组固定常量的时候。当然,这包括“天然的枚举类型”,例如行星、一周的天数以及棋子的数目等等。但它也包括你在编译时就知道其所有可能值的其他集合,例如菜单的选项,操作代码以及命令行标记等。枚举类型中的常量集并不一定要始终保持不变。专门设计枚举特性是考虑到枚举类型的二进制兼容演变。

总而言之,与int常量相比,枚举类型的优势是不言而喻的。枚举要易读得多,也要更安全,功能更加强大。许多枚举都不需要显式的构造器或者成员,但许多其他枚举则受益于“每个常量与属性关联”以及“提供行为受这个属性影响的方法”。只有极少数的枚举受益于将多种行为与单个方法关联。在这种相对少见的情况下,特定于常量的方法要优先于启用自有值的枚举。如果多个枚举常量同时共享相同的行为,则考虑策略枚举。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值