首頁  >  文章  >  Java  >  Java枚舉的使用詳解

Java枚舉的使用詳解

黄舟
黄舟原創
2017-09-09 11:23:181863瀏覽

這篇文章主要介紹了Java枚舉的使用方法詳解的相關資料,希望透過本文大家能掌握枚舉的使用方法,需要的朋友可以參考下

 Java枚舉的使用方法詳解

前言  你程式碼中的flag和status,都應該用枚舉來取代

##很多人都說,枚舉在實際開發中很少用到,甚至沒用到。因為,他們的程式碼往往是這樣子的:



public class Constant { 
  /* 
   * 以下几个变量表示英雄的状态 
   */ 
  public final static int STATUS_WALKING = 0;//走  
  public final static int STATUS_RUNNINGING = 1;//跑 
  public final static int STATUS_ATTACKING = 2;//攻击  
  public final static int STATUS_DEFENDING = 3;//防御  
  public final static int STATUS_DEAD = 4;//挂了  
   
  /* 
   * 以下几个变量表示英雄的等级 
   */ 
  //此处略去N行代码 
}

然後,他們是這樣使用這個類別的:



hero.setStatus(Contant.STATUS_ATTACKING);

嗯,然後他們就說,「我在實際開發中很少用到枚舉」


當然,他們的意思是說很少用到枚舉Enum這個類。


但是,我想說的是,上面這些程式碼,通通應該用Enum去實作。


為什麼?


因為他們的程式碼完全建立在對隊友的信任,假設來了個奇葩隊友,做了這件事:



hero.setStatus(666);

你說,螢幕上的英雄會怎麼樣呢?


總之,假如你在實際程式設計中經常使用這樣的程式碼,那是時候好好學習一下Enum了。

列舉初探   為什麼要使用枚舉類型

#生活中處處都有枚舉,包括“自然的枚舉”,比如行星、一周的天數,也包含我們設計出來的列舉,像是csdn的tab標籤,選單等。


Java程式碼中表示枚舉的方式,大體上有兩種,一是int枚舉,而是Enum枚舉,當然,我們都知道,Enum枚舉才是Java提供的真正枚舉。


那麼,為什麼我們要使用Enum枚舉型別呢?先來看看在Java 1.5之前,沒有枚舉型別時,我們是怎麼表示枚舉的。


以八大行星為例,每個行星對應一個int值,我們大概會這樣寫



public class PlanetWithoutEnum { 
  public static final int PLANET_MERCURY = 0; 
  public static final int PLANET_VENUS = 1; 
  public static final int PLANET_EARTH = 2; 
  public static final int PLANET_MARS = 3; 
  public static final int PLANET_JUPITER = 4; 
  public static final int PLANET_SATURN = 5; 
  public static final int PLANET_URANUS = 6; 
  public static final int PLANET_NEPTUNE = 7; 
}

這種叫int枚舉模式,當然你也可以使用String枚舉模式,無論採用何種方式,這樣的做法,在型別安全和使用方便性上都很差。

如果變數planet表示一個行星,使用者可以給這個值賦與一個不在我們枚舉值裡面的值,例如planet = 9,這是哪個行星估計也只有天知道了; 再者,我們很難計算出到底有多少個行星,我們也很難對行星進行遍歷操作等等。

現在我們用枚舉來創造我們的行星。



public enum Planet { 
  MERCURY, VENUS, EARTH, MARS, JUPITER, SATURN, URANUS, NEPTUNE; 
}

上面這個是最簡單的枚舉,我們姑且叫做Planet 1.0,這個版本的行星枚舉,我們實現了一個功能,就是任何一個Planet類型的變量,都可以由編譯器來保證,任何傳到給參數的非null物件一定屬於這八個行星之一。


然後,我們將Planet升級,Java允許我們為枚舉型別添加任意的方法,這裡引言書中的程式碼,大家自行體會一下枚舉的建構器、公共方法、枚舉遍歷等知識點。



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 WeightTable { 
  public static void main(String[] args) { 
    printfWeightOnAllPlanets(8d); 
  } 
 
  public static void printfWeightOnAllPlanets(double earthWeight) { 
    double mass = earthWeight / Planet.EARTH.surfaceGravity(); 
    for (Planet p : Planet.values()) 
      System.out.printf("Weight on %s is %f%n", p, p.surfaceWeight(mass)); 
  } 
}

執行WeightTable,列印結果如下:



Weight on MERCURY is 3.023254
Weight on VENUS is 7.240408
Weight on EARTH is 8.000000
Weight on MARS is 3.036832
Weight on JUPITER is 20.237436
Weight on SATURN is 8.524113
Weight on URANUS is 7.238844
Weight on NEPTUNE is 9.090108

在這個小程式裡,我們用到了枚舉的values()方法,這個方法回傳了枚舉型別裡的枚舉變數的集合,非常實用。

枚舉進階   計算器運算子列舉類別


上一小節的例子裡,我們用到了枚舉類別的公共方法,這一節,我們以計算器運算符Operation 枚舉類別為例,看看怎麼實現對於

每一個枚舉對象,執行不同的操作。


首先,我們很容易想到的一個方法,在公共方法裡,使用switch去判斷枚舉類型,然後執行不同的操作,程式碼如下:



public enum OperationUseSwitch { 
  PLUS, MINUS, TIMES, pIDE; 
 
  double apply(double x, double y) { 
    switch (this) { 
      case PLUS: 
        return x + y; 
      case MINUS: 
        return x + y; 
      case TIMES: 
        return x + y; 
      case pIDE: 
        return x + y; 
    } 
    // 如果this不属于上面四种操作符,抛出异常 
    throw new AssertionError("Unknown operation: " + this); 
  } 
}

這段程式碼確實實現了我們的需求,但是有兩個弊端。


首先是我們必須在最後拋出例外或在switch裡加上default,不然無法編譯通過,但是很明顯,程式的分支是不會進入例外或default的。


其次,這段程式碼非常脆弱,如果我們新增了新的操作類型,卻忘了在switch裡加入對應的處理邏輯,執行新的運算運算時,就會出現問題。


還好,Java枚舉提供了一個功能,稱為 特定於常數的方法實作。


我們只需要在枚舉型別中宣告一個抽象方法,然後在各個枚舉常數中去覆寫這個方法,實作如下:



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; 
    } 
  }, 
  pIDE { 
    double apply(double x, double y) { 
      return x / y; 
    } 
  }; 
 
  abstract double apply(double x, double y); 
}

這樣,也就再也不會出現新增運算子後忘記新增對應的處理邏輯的情況了,因為編譯器就會提示我們必須覆寫apply方法。


不過,這種 特定於常數的方法實作 有一個缺點,那就是你很難在枚舉常數之間共享程式碼。


我們以星期X的列舉為例,週一到週五是工作日,執行一種邏輯,週六週日,休息日,執行另一種邏輯。


如果还是使用 特定于常量的方法实现,写出来的代码可能就是这样的:


public enum DayUseAbstractMethod { 
  MONDAY { 
    @Override 
    void apply() { 
      dealWithWeekDays();//伪代码 
    } 
  }, 
  TUESDAY { 
    @Override 
    void apply() { 
      dealWithWeekDays();//伪代码 
    } 
  }, 
  WEDNESDAY { 
    @Override 
    void apply() { 
      dealWithWeekDays();//伪代码 
    } 
  }, 
  THURSDAY { 
    @Override 
    void apply() { 
      dealWithWeekDays();//伪代码 
    } 
  }, 
  FRIDAY { 
    @Override 
    void apply() { 
      dealWithWeekDays();//伪代码 
    } 
  }, 
  SATURDAY { 
    @Override 
    void apply() { 
      dealWithWeekEnds();//伪代码 
    } 
  }, 
  SUNDAY { 
    @Override 
    void apply() { 
      dealWithWeekEnds();//伪代码 
    } 
  }; 
 
  abstract void apply(); 
}

很明显,我们这段代码里面有相当多的重复代码。

那么要怎么优化呢,我们不妨这样想,星期一星期二等等是一种枚举,那么工作日和休息日,难道不也是一种枚举吗,我们能不能给Day的构造函数传入一个工作日休息日的DayType枚举呢?这也就是书中给出的一种叫策略枚举 的方法,代码如下:


public enum Day { 
  MONDAY(DayType.WEEKDAY), TUESDAY(DayType.WEEKDAY), WEDNESDAY( 
      DayType.WEEKDAY), THURSDAY(DayType.WEEKDAY), FRIDAY(DayType.WEEKDAY), SATURDAY( 
      DayType.WEEKDAY), SUNDAY(DayType.WEEKDAY); 
  private final DayType dayType; 
 
  Day(DayType daytype) { 
    this.dayType = daytype; 
  } 
 
  void apply() { 
    dayType.apply(); 
  } 
 
  private enum DayType { 
    WEEKDAY { 
      @Override 
      void apply() { 
        System.out.println("hi, weekday"); 
      } 
    }, 
    WEEKEND { 
      @Override 
      void apply() { 
        System.out.println("hi, weekend"); 
      } 
    }; 
    abstract void apply(); 
  } 
}

通过策略枚举的方式,我们把Day的处理逻辑委托给了DayType,个中奥妙,读者可以细细体会。

枚举集合   EnumSet的使用

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<Style> styles) { 
    // Body goes here 
    for(Style style : styles){ 
      System.out.println(style); 
    } 
  } 
 
  // Sample use 
  public static void main(String[] args) { 
    Text text = new Text(); 
    text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC)); 
  } 
}

这个例子里,我们使用了EnumSet.of方法,轻松创建了枚举集合。

枚举Map   EnumMap的使用

假设对于香草(Herb),有一个枚举属性Type(一年生、多年生、两年生)

Herb:


public class Herb { 
  public enum Type { ANNUAL, PERENNIAL, BIENNIAL } 
 
  private final String name; 
  private final Type type; 
   
  Herb(String name, Type type) { 
    this.name = name; 
    this.type = type; 
  } 
 
  @Override public String toString() { 
    return name; 
  } 
}

现在,假设我们有一个Herb数组,我们需要对这个Herb数组按照Type进行分类存放。

所以接下来,我们需要创建一个Map,value肯定是Herb的集合了,那么用什么作为key呢?

有的人会使用枚举类型的ordinal()方法,这个函数返回int类型,表示枚举遍历在枚举类里的位置,这样做,缺点很明显,由于你的key的类型是int,不能保证传入的int一定能和枚举类里的变量对应上。

所以,在key的选择上,毫无疑问,只能使用枚举类型,也即Herb.Type。

最后还有一个问题,要使用什么Map? Java为枚举类型专门提供了一种Map,叫EnumMap,相比较与其他Map,这种Map在处理枚举类型上更快,有兴趣的同学可以研究一下这个map的内部实现。

下面让我们看看怎么使用EnumMap:


public static void main(String[] args) { 
  Herb[] garden = { new Herb("Basil", Type.ANNUAL), 
      new Herb("Carroway", Type.BIENNIAL), 
      new Herb("Dill", Type.ANNUAL), 
      new Herb("Lavendar", Type.PERENNIAL), 
      new Herb("Parsley", Type.BIENNIAL), 
      new Herb("Rosemary", Type.PERENNIAL) }; 
 
  // Using an EnumMap to associate data with an enum - Page 162 
  Map<Herb.Type, Set<Herb>> herbsByType = new EnumMap<Herb.Type, Set<Herb>>( 
      Herb.Type.class); 
  for (Herb.Type t : Herb.Type.values()) 
    herbsByType.put(t, new HashSet<Herb>()); 
  for (Herb h : garden) 
    herbsByType.get(h.type).add(h); 
  System.out.println(herbsByType); 
 
}

总结

和int枚举相比,Enum枚举的在类型安全和使用便利上的优势是不言而喻的。
Enum为枚举提供了丰富的功能,如文章中提到的特定于常量的方法实现和策略枚举。
EnumSet和EnumMap是两个为枚举而设计的集合,在实际开发中,用到枚举集合时,请优先考虑这两个。

以上是Java枚舉的使用詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn