Java 1.5 特性 : Enumerated types

C++ 中的列舉型別

在 C++ 中,有列舉這個型別,其實就是將數個參考值或名稱集合在一起。這種集合的方法,並不是陣列,也不屬於容器。通常我們之它為列舉:

enum Suit { club, diamond, heart, spade };
enum Month { JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC } month;
enum What { abc = 1, def = 10, ghi = -3, jkl = 2, mno = 10 };
typedef enum Bool { false, true } bool;

for (month = JAN; month <= DEC; month++) { // month++ 不一定都能順利被編譯
  printf("%d ", month);
}

在 enum 中第一個名稱若沒有設定初始值時,其預設值為 0,下一位為 1,依此類推。所以 Suit 中,club = 0, diamond = 2, heart = 3, spade = 4。同時上例中的 false = 0, true = 1,因為在 C++ 中早期並沒有布林型別,所以通常我們都會自己使用列舉來模擬。另外從列舉型別 What 中也可看出,列舉的值可正可負,甚至可以重覆,其數值容許型別為 signed int

列舉主要是拿來當常數使用,把相同性質的名稱集合在一起(有時會將名稱給定數值,像上例的 Month),用同一種型別處理各種不同的情況,就像是 Month 型別,有十二種可能的數值。當然,因為是常數,所以你沒辦法再更動每一個名稱的值,像是 JAN = 1,就無法在程式中再修改它的值(除非你直接修改它的初始值定義)。除了 enum 之外,常數也可用 #defineconst 設定,不過這就不在我們討論範圍之內了。

Java 的列舉型別

Java 中新增的列舉型別和 C++ 的概念類似,不過在用法上有很大的不同。首先 C++ 中給定初始值是使用 JAN = 1,但是在 Java 中則需要一些複雜的設定。另外 C++ 對列舉的處理比較偏向於看成基本型別 signed int 的常數集合,而 Java 中則是將 enum 看成類別:

public class EnumeratedTypes1 {
  public enum Month { JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };

  public static void main(String[] args) {
    for (Month month : Month.values()) {
      System.out.print(month + "=");
    }
    System.out.println();
    Month month = Month.JAN;
    System.out.println(month);
    System.out.println(month.getClass());
  }
}

輸出結果:

JAN=FEB=MAR=APR=MAY=JUN=JUL=AUG=SEP=OCT=NOV=DEC=
JAN
class EnumeratedTypes1$Month

由上例中的輸出結果可看出,Month 是一個類別,姑且可稱之為列舉類別。會有這種結果,其實是可以理解的。因為列舉本身就是一種型別,在 C++ 中對其有特殊的設計,並且在 ++ 的處理上,有些編譯器就無法通過,因為這些編譯器把列舉看成類別,因為對 ++ 的部份我們要自己重載運算子(但有趣的是 =<= 這些運算子不用重載)。而 Java 語言中則完完全全地是個類別,從 getClass() 中即可看出來。

更複雜的列舉定義

底下示範了一個比較複雜的例子,由這個例子中可以看出,列舉其實只是一種比較特殊的類別,所以我們也才一直稱呼 enum 為列舉「型別」。它可以擁有自訂建構式、欄位以及方法,使得我們在應用上更方便,當然,C++ 中也可以做到類似的事。

public class EnumeratedTypes2 {
  enum Suit { club(1), diamond(2), heart(3), spade(4);
    private int value; // 自訂欄位
    Suit(int value) { // 因為設定 club(1), ..,所以必須要撰寫此一建構式
      this.value = value;
    }
    public int getValue() { // 自訂取值方法
      return value;
    }
    public void setValue(int value) { // 自訂設值方法
      this.value = value;
    }
  };

  public static void main(String[] args) {
    for (Suit suit : Suit.values()) {
        System.out.println(suit + "=" + suit.getValue());
    }
    Suit suit = Suit.diamond;
    System.out.println(suit.getValue());
    suit.setValue(10);
    System.out.println(suit.getValue());
  }
}

輸出結果:

club=1
diamond=2
heart=3
spade=4
2
10

之前提到列舉型別可看成常數,但是在這裡我們自訂方法 setValue 後,會發現竟然可以重新指定數值。這不就和常數相衝突了?其實不然,在 C++ 中因為直接把列舉子(club, JAN 等名稱叫做列舉子)對應到整數,所以我們也就不能再修改這些數值。但在 Java 中,列舉的字串值其實是它本身的名稱,也就是我們使用 System.out.println(Suit.club) 會在螢幕上顯示 club 字串,這才是列舉型別真正的常數值,而上例中的 value 只是我們自行增加的欄位,可以自由設定,這和 enum 本身數值的常數定義並無衝突。不過我建議為了省去不必要的麻煩,最好還是宣告為 private final int value; 會比較好些,以免不小心修改到它而使得程式中出現莫名的 bug。

命名空間的問題

在 C++ 中如果列舉中有相同的名稱,那麼可能會造成一些命名重複的問題:

enum fruit { 
  orange=0,
  apple=1
};
enum color {
  orange=1, // multiply defined
  green=2
};

enum color = orange; // 到底是那個 orange?
// enum color = color::orange; // 無法使用此種方法指定

不過在 Java 中,因為完完全全以類別來處理,所以我們在設定時必須要加上類別名稱,就像是在存取類別中靜態常數值一樣,從而避免了上述命名衝突的情況:

public class EnumeratedTypes3 {
  enum Color { RED, GREEN, BLUE, ORANGE };
  enum Fruit { BANANA, ORANGE, PEACH };

  public static void main(String[] args) {
    //Color color = ORANGE; // 無法通過編譯
    Color color = Color.ORANGE; // 必須指定類別才能通過編譯
    System.out.println(color);
    Fruit fruit = Fruit.ORANGE;
    System.out.println(fruit);
  }
}

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

2 + one =

返回頂端