枚舉類型入門(轉載自IBM開發者)

Tiger 中的一個重要新特性是枚舉構造,它是一種新的類型,允許用常量來表示特定的數據
片斷,而且全部都以類型安全的形式來表示。Tiger 專家、developerWorks 的多產作者
Brett McLaughlin
將解釋枚舉的定義,介紹如何在應用程序中運用枚舉,以及它爲什麼能夠讓您拋棄所有舊的
public static final 代碼。
您已經知道,Java 代碼的兩個基本的構造塊是類 和接口。現在 Tiger 又引入了枚舉,一
般簡稱它爲 enum。這個新類型允許您表示特定的數據點,這些數據點只接受分配時預先定
義的值集合。

當然,熟練的程序員可以用靜態常量實現這項功能,如清單 1 所示:

清單 1. public static final 的常量

public class OldGrade {

  public static final int A = 1;
  public static final int B = 2;
  public static final int C = 3;
  public static final int D = 4;
  public static final int F = 5;
  public static final int INCOMPLETE = 6;
}

 

說明:我要感謝 O'Reilly 媒體公司,該公司允許在本文中使用我撰寫的 Java 1.5 Tiger:
A Developer's Notebook 一書中“枚舉”這一章中的代碼示例(請參閱參考資料)。

然後您就可以讓類接受像 OldGrade.B 這樣的常量,但是在這樣做的時候,請記住這類常量
是 Java 中 int 類型的常量,這意味着該方法可以接受任何 int 類型的值,即使它和
OldGrade
中定義的所有級別都不對應。因此,您需要檢測上界和下界,在出現無效值的時候,可能還
要包含一個 IllegalArgumentException。而且,如果後來又添加另外一個級別(例如
OldGrade.WITHDREW_PASSING),那麼必須改變所有代碼中的上界,才能接受這個新值。

換句話說,在使用這類帶有整型常量的類時,該解決方案也許可行,但並不是非常有效。幸
運的是,枚舉提供了更好的方法。

定義枚舉
清單 2 使用了一個可以提供與清單 1 相似的功能的枚舉:

清單 2. 簡單的枚舉類型

package com.oreilly.tiger.ch03;

public enum Grade {
  A, B, C, D, F, INCOMPLETE
};

 

在這裏,我使用了新的關鍵字 enum,爲 enum 提供了一個名稱,並指定了允許的值。然後
,Grade 就變成了一個枚舉類型,您可以按清單 3 所示的方法使用它:

清單 3. 使用枚舉類型

package com.oreilly.tiger.ch03;

public class Student {

  private String firstName;
  private String lastName;
  private Grade grade;

  public Student(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public String getLastName() {
    return lastName;
  }

  public String getFullName() {
    return new StringBuffer(firstName)
           .append(" ")
           .append(lastName)
           .toString();
  }

  public void assignGrade(Grade grade) {
    this.grade = grade;
  }

  public Grade getGrade() {
    return grade;
  }
}

 

用以前定義過的類型建立一個新的枚舉(grade)之後,您就可以像使用其他成員變量一樣
使用它了。當然,枚舉只能分配枚舉值中的一個(例如,A、C 或 INCOMPLETE)。而且,在
assignGrade() 中是沒有進行錯誤檢測的代碼,也沒有考慮邊界情況,請注意這是如何做到

使用枚舉值
迄今爲止,您所看到的示例都相當簡單,但是枚舉類型提供的東西遠不止這些。您可以逐個
遍歷枚舉值,也可以在 switch 語句中使用枚舉值,枚舉是非常有價值的。

遍歷枚舉值
下面我們用一個示例顯示如何遍歷枚舉類型的值。清單 4 所示的這項技術,適用於調試、
快速打印任務以及把枚舉加載到集合(我很快將談到)中的工具:

清單 4. 遍歷枚舉值

public void listGradeValues(PrintStream out) throws IOException {
  for (Grade g : Grade.values()) {
    out.println("Allowed value: '" + g + "'");
  }
}

 

運行這段代碼,將得到清單 5 所示的輸出:

清單 5. 迭代操作的輸出

Allowed Value: 'A'
Allowed Value: 'B'
Allowed Value: 'C'
Allowed Value: 'D'
Allowed Value: 'F'
Allowed Value: 'INCOMPLETE'

 

這裏有許多東西。首先,我使用了 Tiger 的新的 for/in 循環(也叫作 foreach 或 增強
的 for)。另外,您可以看到 values() 方法返回了一個由獨立的 Grade 實例構成的數組
,每個數組都有一個枚舉類型的值。換句話說,values() 的返回值是 Grade[]。

在枚舉間切換
能夠在枚舉的值之間移動很好,但是更重要的是根據枚舉的值進行決策。您當然可以寫一堆
if (grade.equals(Grade.A)) 類型的語句,但那是在浪費時間。Tiger 能夠很方便地把枚
舉支持添加到過去的好東西 switch 語句上,所以它很容易使用,而且適合您已知的內容。
清單 6
向將展示如何解決這個難題:

清單 6. 在枚舉之間切換

public void testSwitchStatement(PrintStream out) throws IOException {
  StringBuffer outputText = new StringBuffer(student1.getFullName());

  switch (student1.getGrade()) {
    case A:
      outputText.append(" excelled with a grade of A");
      break;
    case B: // fall through to C
    case C:
      outputText.append(" passed with a grade of ")
                .append(student1.getGrade().toString());
      break;
    case D: // fall through to F
    case F:
      outputText.append(" failed with a grade of ")
                .append(student1.getGrade().toString());
      break;
    case INCOMPLETE:
      outputText.append(" did not complete the class.");
      break;
  }

  out.println(outputText.toString());
}

 

在這裏,枚舉值被傳遞到 switch 語句中(請記住,getGrade() 是作爲 Grade 的實例返回
的),而每個 case 子句將處理一個特定的值。該值在提供時沒有枚舉前綴,這意味着不用
將代碼寫成 case Grade.A,只需將其寫成 case A 即可。如果您不這麼做,編譯器不會接
受有前綴的值。

現在,您應該已經瞭解使用 switch 語句時的基本語法,但是還有一些事情您需要知道。
在使用 switch 之前進行計劃
正如您所期待的,在使用枚舉和 switch 時,您可以使用 default 語句。清單 7 顯示了這
個用法:

清單 7. 添加一個 default 塊

public void testSwitchStatement(PrintStream out) throws IOException {
  StringBuffer outputText = new StringBuffer(student1.getFullName());

  switch (student1.getGrade()) {
    case A:
      outputText.append(" excelled with a grade of A");
      break;
    case B: // fall through to C
    case C:
      outputText.append(" passed with a grade of ")
                .append(student1.getGrade().toString());
      break;
    case D: // fall through to F
    case F:
      outputText.append(" failed with a grade of ")
                .append(student1.getGrade().toString());
      break;
    case INCOMPLETE:
      outputText.append(" did not complete the class.");
      break;
    default:
      outputText.append(" has a grade of ")
                .append(student1.getGrade().toString());
      break;
  }

  out.println(outputText.toString());
}

 

研究以上代碼可以看出,任何沒有被 case 語句處理的枚舉值都會被 default 語句處理。
這項技術您應當堅持採用。原因是:假設 Grade 枚舉被您的小組中其他程序員修改(而且
他忘記告訴您這件事)成清單 8 所示的版本:

清單 8. 給 Grade 枚舉添加一個值

package com.oreilly.tiger.ch03;

public enum Grade {
  A, B, C, D, F, INCOMPLETE,
  WITHDREW_PASSING, WITHDREW_FAILING
};

 

現在,如果使用清單 6 的代碼所示的新版 Grade,那麼這兩個新值會被忽略。更糟的是,
您甚至看不到錯誤!在這種情況下,存在某種能夠通用的 default 語句是非常重要的。清
單 7
無法很好地處理這些值,但是它會提示您還有其他值,您需要處理這些值。一旦完成處理,
您就會有一個繼續運行的應用程序,而且它不會忽略這些值,甚至還會指導您下一步的動作
。所以這是一個良好的編碼習慣。

枚舉和集合
您所熟悉的使用 public static final 方法進行編碼的那些東西,可能已經轉而採用枚舉
的值作爲映射的鍵。如果您不知道其中的含義,請參見清單 9,它是一個公共錯誤信息的示
例,在使用 Ant 的 build 文件時,可能會彈出這樣的消息,如下所示:

清單 9. Ant 狀態碼

package com.oreilly.tiger.ch03;

public enum AntStatus {
  INITIALIZING,
  COMPILING,
  COPYING,
  JARRING,
  ZIPPING,
  DONE,
  ERROR
}

 

爲每個狀態碼分配一些人們能讀懂的錯誤信息,從而允許人們在 Ant 提供某個代碼時查找
合適的錯誤信息,將這些信息顯示在控制檯上。這是映射(Map)的一個絕好用例,在這裏
,每個映射(Map)的鍵都是一個枚舉值,而每個值都是鍵的錯誤信息。清單 10 演示了該
映射的工作方式:

清單 10. 枚舉的映射(Map)

public void testEnumMap(PrintStream out) throws IOException {
  // Create a map with the key and a String message
  EnumMap<AntStatus, String> antMessages =
    new EnumMap<AntStatus, String>(AntStatus.class);

  // Initialize the map
  antMessages.put(AntStatus.INITIALIZING, "Initializing Ant...");
  antMessages.put(AntStatus.COMPILING,    "Compiling Java classes...");
  antMessages.put(AntStatus.COPYING,      "Copying files...");
  antMessages.put(AntStatus.JARRING,      "JARring up files...");
  antMessages.put(AntStatus.ZIPPING,      "ZIPping up files...");
  antMessages.put(AntStatus.DONE,         "Build complete.");
  antMessages.put(AntStatus.ERROR,        "Error occurred.");

  // Iterate and print messages
  for (AntStatus status : AntStatus.values() ) {
    out.println("For status " + status + ", message is: " +
                antMessages.get(status));
  }
}

 

該代碼使用了泛型(generics)(請參閱參考資料)和新的 EnumMap 構造來建立新映射。
而且,枚舉值是通過其 Class 對象提供的,同時提供的還有映射值的類型(在該例中,它
只是一個簡單的字符串)。該方法的輸出如清單 11 所示:

枚舉的 Class 對象?
您可能已經注意到,清單 10 中的示例代碼實際上表明 Tiger 把枚舉當作類,這可以從
AntStatus 的 Class 對象那裏得到證明,該對象不僅可用,而且正被實際使用。這是真的
。歸根到底, Tiger 還是把枚舉看成是特殊的類類型。有關枚舉的具體實現細節,請參閱
Java 5.0 Tiger: A
Developer's Notebook 的第三章(請參閱參考資料)。


清單 11. 清單 10 的輸出

[echo] Running AntStatusTester...
[java] For status INITIALIZING, message is: Initializing Ant...
[java] For status COMPILING, message is: Compiling Java classes...
[java] For status COPYING, message is: Copying files...
[java] For status JARRING, message is: JARring up files...
[java] For status ZIPPING, message is: ZIPping up files...
[java] For status DONE, message is: Build complete.
[java] For status ERROR, message is: Error occurred.

 

更進一步
枚舉也可以與集合結合使用,而且非常像新的 EnumMap 構造,Tiger 提供了一套新的
EnumSet
實現,允許您使用位操作符。另外,可以爲枚舉添加方法,用它們實現接口,定義叫作特定
值的類的實體,在該實體中,特定的代碼被附加到枚舉的具體值上。這些特性超出了本文的
範圍,但是在其他地方,有詳細介紹它們的文檔(請參閱參考資料)。

使用枚舉,但是不要濫用
學習任何新版語言的一個危險就是瘋狂使用新的語法結構。如果這樣做,那麼您的代碼就會
突然之間有 80% 是泛型、標註和枚舉。所以,應當只在適合使用枚舉的地方纔使用它。那
麼,枚舉在什麼地方適用呢?一條普遍規則是,任何使用常量的地方,例如目前用 switch
代碼切換常量的地方。如果只有單獨一個值(例如,鞋的最大尺寸,或者籠子中能裝猴子的
最大數目),則還是把這個任務留給常量吧。但是,如果定義了一組值,而這些值中的任何
一個都可以用於特定的數據類型,那麼將枚舉用在這個地方最適合不過。

參考資料

您可以參閱本文在 developerWorks 全球站點上的 英文原文。


下載 Tiger 並自己試用。


官方的 J2SE 5.0 home page 是您不能遺漏的全面資源。


有關 Tiger 的特定內容,請參閱 John Zukowski 撰寫的 Taming Tiger 系列文章,其中提
供了有關 J2SE 5.0 中新增內容和變化內容的簡短提示。


Brett McLaughlin 還撰寫了兩篇關於 Tiger 中的標註的系列文章:Tiger 中的註釋,第 1
部分: 向 Java 代碼中添加元數據 和 Tiger 中的註釋,第 2 部分: 定製註釋。


Java 1.5 Tiger: A Developer's Notebook (O'Reilly & Associates; 2004 年)的作者
是 Brett McLaughlin and David Flanagan,這本書介紹了差不多所有 Tiger 的最新特性
(包括標註),該書的格式以代碼爲核心,適用於開發人員。


在 developerWorks Java 技術專區中,可以找到有關 Java 編程各個方面的數百篇文章。


請訪問 Developer Bookstore,獲得技術書籍的完整清單,其中包括數百本 Java 相關主題
的書籍。

發佈了12 篇原創文章 · 獲贊 0 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章