提出问题

项目中如何使用枚举和注解???

解决问题

用enum替换int常量

例如:下面是公司项目的一个标准的enum实例。

  1. package com.evada.de.common.enums;
  2. /**
  3. * 状态枚举
  4. * @author Ay
  5. */
  6. public enum StatusEnum {
  7. /** 0:已删除 */
  8. DELETED("0","已删除"),
  9. /** 1:启用 */
  10. ENABLE("1","启用"),
  11. /** 2:禁用 */
  12. DISABLE("2","禁用");
  13. /** 状态值 */
  14. private final String code;
  15. private final String name;
  16. private StatusEnum(String code, String name) {
  17. this.code = code;
  18. this.name = name;
  19. }
  20. @Override
  21. public String toString() {
  22. return code;
  23. }
  24. public String getName() {
  25. return name;
  26. }
  27. }

注意,与枚举常量关联的行为,最好被实现成私有的或者包级私有的方法。

例如:

  1. package com.evada.de;
  2. /**
  3. * Created by Ay on 2016/10/2.
  4. */
  5. public enum Operation {
  6. PLUS,MINUS,TIMES,DIVIDE;
  7. double apply(double x,double y){
  8. switch (this){
  9. case PLUS:return x + y;
  10. case MINUS:return x - y;
  11. case TIMES:return x * y;
  12. case DIVIDE:return x / y;
  13. }
  14. throw new AssertionError("Unknow op: " + this);
  15. }
  16. }

上面代码是很脆弱的,如果添加一种类型,却忘记给switch添加相应的条件,枚举仍然可以编译,但是当你试图运用新的运算时,就会运行失败。

修改过后的实例:

  1. package com.evada.de;
  2. /**
  3. * Created by Ay on 2016/10/2.
  4. */
  5. public enum Operation {
  6. PLUS("+"){
  7. @Override
  8. double apply(double x, double y) {
  9. return x + y;
  10. }
  11. },
  12. MINUS("-"){
  13. @Override
  14. double apply(double x, double y) {
  15. return x - y;
  16. }
  17. },
  18. TIMES("*"){
  19. @Override
  20. double apply(double x, double y) {
  21. return x * y;
  22. }
  23. },
  24. DIVIDE("/"){
  25. @Override
  26. double apply(double x, double y) {
  27. return x / y;
  28. }
  29. };
  30. private final String symbol;
  31. Operation(String symbol){this.symbol = symbol;}
  32. @Override
  33. public String toString() { return this.symbol;}
  34. abstract double apply(double x,double y);
  35. }

下面看一个嵌套枚举实例:个人感觉这段代码很优雅,贴出来相互学习下:

如果多个枚举常量同时共享相同的行为,则考虑策略枚举(嵌套枚举)。

  1. /**
  2. * Created by Ay on 2016/10/2.
  3. */
  4. public enum PayrollDay {
  5. MonDAY(PayType.WEEKDAY),
  6. WEEKDAY(PayType.WEEKDAY),
  7. TUESDAY(PayType.WEEKDAY),
  8. WENDESDAY(PayType.WEEKDAY),
  9. THURSDAY(PayType.WEEKDAY),
  10. FRIDAY(PayType.WEEKDAY),
  11. SATURDAY(PayType.WEEKEND),
  12. SUNDAY(PayType.WEEKEND);
  13. private PayType payType;
  14. PayrollDay(PayType payType){
  15. this.payType = payType;
  16. }
  17. double pay(double hoursWorked,double payRate){
  18. return payType.pay(hoursWorked,payRate);
  19. }
  20. //这里是嵌套枚举
  21. private enum PayType{
  22. WEEKDAY{
  23. @Override
  24. double overtimePay(double hrs, double payRate) {
  25. return 0;
  26. }
  27. },WEEKEND{
  28. @Override
  29. double overtimePay(double hrs, double payRate) {
  30. return 0;
  31. }
  32. };
  33. private static final int HOURS_PRE_SHIFT = 8;
  34. abstract double overtimePay(double hrs,double payRate);
  35. double pay(double hoursWorked,double payRate){
  36. double basePay = hoursWorked * payRate;
  37. return basePay + overtimePay(hoursWorked,payRate);
  38. }
  39. }
  40. }

枚举有个小小的性能缺点,即装载和初始化枚举时会有空间和时间成本。

总而言之,与int常量相比,枚举类型的优势是不可言喻的。枚举要易读得多,也更加安全,功能更加强大。

用实例域替换序数

例如:

  1. enum Ensemble{
  2. SOLO,DUET,TRIO,QUARTET,QUINTET,SEXTET,SEPTEX,OCTEX,NONET,DECTET;
  3. public int numberOfMusicians(){
  4. return ordinal() + 1;//ordinal()用来返回枚举常量在类型中的数字位置。
  5. }
  6. }

永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域中:

正确做法是:

  1. enum Ensemble{
  2. SOLO(1),DUET(2),TRIO(3),QUARTET(4),QUINTET(5),
  3. SEXTET(6),SEPTEX(7),OCTEX(8),NONET(9),DECTET(10);
  4. private final int numberOfMusicians;
  5. Ensemble(int size){
  6. this.numberOfMusicians = size;
  7. }
  8. public int numberOfMusicians(){
  9. return ordinal() + 1;
  10. }
  11. }

Enum规范中写道:大多数程序员都不需要这个方法。除非你在编写的是这种数据结构,否则最好避免使用original方法。

用EnumSet代替位域

用OR位运算,将几个常量合并到一个集合中,称为位域。

  1. class Text{
  2. public static final int STYLE_BOLE = 1 << 0;
  3. public static final int STYLE_ITALIC = 1 <<1;
  4. public static final int STYLE_UNDERLINE = 1 <<2;
  5. public static final int STYLE_STRIKETHROUGH = 1 <<3;
  6. public void applyStyles(int styles){ ... }
  7. }

将前一个范例改为用枚举代替位域后的代码为:

  1. class Text{
  2. public enum Style{BOLE,ITALIC,UNDERLINE,STRIKETHROUGH}
  3. public void applyStyles(Set<Style> styles){}
  4. }

总而言之,正是因为枚举类型要用在集合中,所以没有理由用位域来表示它。

用EnumMap代替序数索引

说白了,就是类似于key和value形式,具体可以看《Effective Java》的具体实例。

总而言之,最好不要用序数来索引数组,而要使用EnumMap。如果你所表示的这种关系是多维的,就使用EnumMap<…,EnumMap<…>>。运用程序的程序员在一般情况下都不使用Enum.oridinal,即使要用也很少,因此这是一种特殊情况。

用接口模拟可伸缩的枚举
例如:

  1. public interface Operation{
  2. double apply(double x,double y);
  3. }
  4. public enum BasicOperation implements Operation{
  5. PLUS("+"){
  6. public double apply(double x,double y){ return x + y;}
  7. },
  8. MINUS("-"){
  9. public double apply(double x,double y){ return x - y;}
  10. },
  11. TIMES("*"){
  12. public double apply(double x,double y){ return x * y;}
  13. },
  14. DIVIDE("/"){
  15. public double apply(double x,double y){ return x / y;}
  16. };
  17. private final String symbool;
  18. BasicOperation(String symbool){ this.symbool = symbool}
  19. @Override
  20. public String toString() {
  21. return symbool;
  22. }
  23. @Override
  24. public double apply(double x, double y) {
  25. return 0;
  26. }
  27. }

上面例子有很好的扩展性,假设你想要定义一个上述操作类型的扩展,由求幂和求余操作组成,你需要做的就是编写一个枚举类型,让它实现Operation接口:

public interface Operation{
   double apply(double x,double y);
}
public enum BasicOperation implements Operation{
   PLUS("^"){
       public double apply(double x,double y){ return Math.pow(x,y);}
   },
   MINUS("%"){
       public double apply(double x,double y){ return x % y;}
   };
   private final String symbool;
   BasicOperation(String symbool){ this.symbool = symbool}
   @Override
   public String toString() {
       return symbool;
   }
   @Override
   public double apply(double x, double y) {
       return 0;
   }
}

总而言之,虽然无法编写可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型,对它进行模拟。这样允许客户端编写自己的枚举来实现接口。如果API是根据接口编写的,那么在可以使用基础枚举类型的任何地方,也都可以使用这些枚举。

注解优先于命名模式

命名模式:表明有些程序元素需要通过某种工具或者框架进行特殊处理。例如,Junit测试框架原本要求它的用户一定要用test作为测试方法名称的开头。

例如:

@Test
public void testLove(){
   System.out.println("I love you!!!");

总而言之,使用注解而不是命名模式

坚持使用Override注解

@Override只能用在方法声明中,它表示被注解的方法声明覆盖了超类型中的一个声明。如果坚持使用这个注解,可以防止一大类的非法错误。

在你想要覆盖超类声明的每个方法声明中使用Override注解。

总而言之,如果在你想要的每个方法声明中使用Override注解来覆盖超类声明,编译器就可以防止大量的错误,但有一个例外。在具体的类中,不必标注你确信覆盖了抽象方法声明的方法。

用标记接口定义类型

标记接口:是没有包含方法声明的接口,而只是指明一个类实现了具有某种属性的接口。例如:Serializable.

标记接口胜过标记注解:标记接口定义的类型是由被标记类的实例实现的;标记注解则没有定义这样的类型。

标记注解胜过标记接口:它可以通过默认的方式添加一个或者多个注解类型的元素,给已被使用的注解类型添加更多的信息。

那么该如何选择?

如果标记是运用到任何程序元素而不是类或者接口,就必须使用注解,因为只有类和接口可以用来实现或者扩展接口。如果标记只运用给类和接口,就要问问自己:我要编写一个还是多个只接受有这种标记的方法?如果是这种情况,就应该优先使用标记接口而非注解。这样你就可以用接口作为相关方法的参数类型,它真正可以为你提供编译时进行类型检查的好处。