302班

          java突擊隊(duì)
          posts - 151, comments - 74, trackbacks - 0, articles - 14
            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          Brett McLaughlin (brett@newInstance.com), 作者/編輯, O'Reilly Media, Inc.
          2004 年 11 月 09 日

          Tiger 中的一個(gè)重要新特性是枚舉構(gòu)造,它是一種新的類型,允許用常量來(lái)表示特定的數(shù)據(jù)片斷,而且全部都以類型安全的形式來(lái)表示。Tiger 專家、developerWorks 的多產(chǎn)作者 Brett McLaughlin 將解釋枚舉的定義,介紹如何在應(yīng)用程序中運(yùn)用枚舉,以及它為什么能夠讓您拋棄所有舊的 public static final 代碼。

          您已經(jīng)知道,Java 代碼的兩個(gè)基本的構(gòu)造塊是 接口。現(xiàn)在 Tiger 又引入了 枚舉,一般簡(jiǎn)稱它為 enum。這個(gè)新類型允許您表示特定的數(shù)據(jù)點(diǎn),這些數(shù)據(jù)點(diǎn)只接受分配時(shí)預(yù)先定義的值集合。

          當(dāng)然,熟練的程序員可以用靜態(tài)常量實(shí)現(xiàn)這項(xiàng)功能,如清單 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;
                      }
                      

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

          然后您就可以讓類接受像 OldGrade.B 這樣的常量,但是在這樣做的時(shí)候,請(qǐng)記住這類常量是 Java 中 int 類型的常量,這意味著該方法可以接受任何 int 類型的值,即使它和 OldGrade 中定義的所有級(jí)別都不對(duì)應(yīng)。因此,您需要檢測(cè)上界和下界,在出現(xiàn)無(wú)效值的時(shí)候,可能還要包含一個(gè) IllegalArgumentException 。而且,如果后來(lái)又添加另外一個(gè)級(jí)別(例如 OldGrade.WITHDREW_PASSING ),那么必須改變所有代碼中的上界,才能接受這個(gè)新值。

          換句話說(shuō),在使用這類帶有整型常量的類時(shí),該解決方案也許可行,但并不是非常有效。幸運(yùn)的是,枚舉提供了更好的方法。





          定義枚舉

          清單 2 使用了一個(gè)可以提供與清單 1 相似的功能的枚舉:



          清單 2. 簡(jiǎn)單的枚舉類型
          package com.oreilly.tiger.ch03;
                      public enum Grade {
                      A, B, C, D, F, INCOMPLETE
                      };
                      

          在這里,我使用了新的關(guān)鍵字 enum ,為 enum 提供了一個(gè)名稱,并指定了允許的值。然后, Grade 就變成了一個(gè) 枚舉類型,您可以按清單 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;
                      }
                      }
                      

          用以前定義過(guò)的類型建立一個(gè)新的枚舉( grade )之后,您就可以像使用其他成員變量一樣使用它了。當(dāng)然,枚舉只能分配枚舉值中的一個(gè)(例如, A 、 CINCOMPLETE )。而且,在 assignGrade() 中是沒(méi)有進(jìn)行錯(cuò)誤檢測(cè)的代碼,也沒(méi)有考慮邊界情況,請(qǐng)注意這是如何做到。







          使用枚舉值

          迄今為止,您所看到的示例都相當(dāng)簡(jiǎn)單,但是枚舉類型提供的東西遠(yuǎn)不止這些。您可以逐個(gè)遍歷枚舉值,也可以在 switch 語(yǔ)句中使用枚舉值,枚舉是非常有價(jià)值的。

          遍歷枚舉值

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



          清單 4. 遍歷枚舉值
          public void listGradeValues(PrintStream out) throws IOException {
                      for (Grade g : Grade.values()) {
                      out.println("Allowed value: '" + g + "'");
                      }
                      }
                      

          運(yùn)行這段代碼,將得到清單 5 所示的輸出:



          清單 5. 迭代操作的輸出
          Allowed Value: 'A'
                      Allowed Value: 'B'
                      Allowed Value: 'C'
                      Allowed Value: 'D'
                      Allowed Value: 'F'
                      Allowed Value: 'INCOMPLETE'
                      

          這里有許多東西。首先,我使用了 Tiger 的新的 for/in 循環(huán)(也叫作 foreach增強(qiáng)的 for )。另外,您可以看到 values() 方法返回了一個(gè)由獨(dú)立的 Grade 實(shí)例構(gòu)成的數(shù)組,每個(gè)數(shù)組都有一個(gè)枚舉類型的值。換句話說(shuō), values() 的返回值是 Grade[]

          在枚舉間切換

          能夠在枚舉的值之間移動(dòng)很好,但是更重要的是根據(jù)枚舉的值進(jìn)行決策。您當(dāng)然可以寫一堆 if (grade.equals(Grade.A)) 類型的語(yǔ)句,但那是在浪費(fèi)時(shí)間。Tiger 能夠很方便地把枚舉支持添加到過(guò)去的好東西 switch 語(yǔ)句上,所以它很容易使用,而且適合您已知的內(nèi)容。清單 6 向?qū)⒄故救绾谓鉀Q這個(gè)難題:



          清單 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 語(yǔ)句中(請(qǐng)記住, getGrade() 是作為 Grade 的實(shí)例返回的),而每個(gè) case 子句將處理一個(gè)特定的值。該值在提供時(shí)沒(méi)有枚舉前綴,這意味著不用將代碼寫成 case Grade.A ,只需將其寫成 case A 即可。如果您不這么做,編譯器不會(huì)接受有前綴的值。

          現(xiàn)在,您應(yīng)該已經(jīng)了解使用 switch 語(yǔ)句時(shí)的基本語(yǔ)法,但是還有一些事情您需要知道。

          在使用 switch 之前進(jìn)行計(jì)劃

          正如您所期待的,在使用枚舉和 switch 時(shí),您可以使用 default 語(yǔ)句。清單 7 顯示了這個(gè)用法:



          清單 7. 添加一個(gè) 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());
                      }
                      

          研究以上代碼可以看出,任何沒(méi)有被 case 語(yǔ)句處理的枚舉值都會(huì)被 default 語(yǔ)句處理。這項(xiàng)技術(shù)您應(yīng)當(dāng) 堅(jiān)持采用。原因是:假設(shè) Grade 枚舉被您的小組中其他程序員修改(而且他忘記告訴您這件事)成清單 8 所示的版本:



          清單 8. 給 Grade 枚舉添加一個(gè)值
          package com.oreilly.tiger.ch03;
                      public enum Grade {
                      A, B, C, D, F, INCOMPLETE
                      ,
                      WITHDREW_PASSING, WITHDREW_FAILING
                      };
                      

          現(xiàn)在,如果使用清單 6 的代碼所示的新版 Grade ,那么這兩個(gè)新值會(huì)被忽略。更糟的是,您甚至看不到錯(cuò)誤!在這種情況下,存在某種能夠通用的 default 語(yǔ)句是非常重要的。清單 7 無(wú)法很好地處理這些值,但是它會(huì)提示您還有其他值,您需要處理這些值。一旦完成處理,您就會(huì)有一個(gè)繼續(xù)運(yùn)行的應(yīng)用程序,而且它不會(huì)忽略這些值,甚至還會(huì)指導(dǎo)您下一步的動(dòng)作。所以這是一個(gè)良好的編碼習(xí)慣。







          枚舉和集合

          您所熟悉的使用 public static final 方法進(jìn)行編碼的那些東西,可能已經(jīng)轉(zhuǎn)而采用枚舉的值作為映射的鍵。如果您不知道其中的含義,請(qǐng)參見(jiàn)清單 9,它是一個(gè)公共錯(cuò)誤信息的示例,在使用 Ant 的 build 文件時(shí),可能會(huì)彈出這樣的消息,如下所示:



          清單 9. Ant 狀態(tài)碼
          package com.oreilly.tiger.ch03;
                      public enum AntStatus {
                      INITIALIZING,
                      COMPILING,
                      COPYING,
                      JARRING,
                      ZIPPING,
                      DONE,
                      ERROR
                      }
                      

          為每個(gè)狀態(tài)碼分配一些人們能讀懂的錯(cuò)誤信息,從而允許人們?cè)?Ant 提供某個(gè)代碼時(shí)查找合適的錯(cuò)誤信息,將這些信息顯示在控制臺(tái)上。這是 映射(Map) 的一個(gè)絕好用例,在這里,每個(gè) 映射(Map) 的鍵都是一個(gè)枚舉值,而每個(gè)值都是鍵的錯(cuò)誤信息。清單 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)(請(qǐng)參閱 參考資料)和新的 EnumMap 構(gòu)造來(lái)建立新映射。而且,枚舉值是通過(guò)其 Class 對(duì)象提供的,同時(shí)提供的還有映射值的類型(在該例中,它只是一個(gè)簡(jiǎn)單的字符串)。該方法的輸出如清單 11 所示:

          枚舉的 Class 對(duì)象?

          您可能已經(jīng)注意到,清單 10 中的示例代碼實(shí)際上表明 Tiger 把枚舉當(dāng)作類,這可以從 AntStatusClass 對(duì)象那里得到證明,該對(duì)象不僅可用,而且正被實(shí)際使用。這是真的。歸根到底, Tiger 還是把枚舉看成是特殊的類類型。有關(guān)枚舉的具體實(shí)現(xiàn)細(xì)節(jié),請(qǐng)參閱 Java 5.0 Tiger: A Developer's Notebook的第三章(請(qǐng)參閱 參考資料)。



          清單 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.
                      







          更進(jìn)一步

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







          使用枚舉,但是不要濫用

          學(xué)習(xí)任何新版語(yǔ)言的一個(gè)危險(xiǎn)就是瘋狂使用新的語(yǔ)法結(jié)構(gòu)。如果這樣做,那么您的代碼就會(huì)突然之間有 80% 是泛型、標(biāo)注和枚舉。所以,應(yīng)當(dāng)只在適合使用枚舉的地方才使用它。那么,枚舉在什么地方適用呢?一條普遍規(guī)則是,任何使用常量的地方,例如目前用 switch 代碼切換常量的地方。如果只有單獨(dú)一個(gè)值(例如,鞋的最大尺寸,或者籠子中能裝猴子的最大數(shù)目),則還是把這個(gè)任務(wù)留給常量吧。但是,如果定義了一組值,而這些值中的任何一個(gè)都可以用于特定的數(shù)據(jù)類型,那么將枚舉用在這個(gè)地方最適合不過(guò)。

          主站蜘蛛池模板: 香格里拉县| 隆德县| 洞头县| 阳谷县| 南昌市| 南郑县| 绍兴市| 宁阳县| 长葛市| 资溪县| 奈曼旗| 涞源县| 青海省| 独山县| 九龙坡区| 东至县| 东辽县| 新和县| 营山县| 崇义县| 新竹市| 江华| 南乐县| 盘山县| 上蔡县| 五大连池市| 晋城| 登封市| 曲松县| 台湾省| 苏州市| 高淳县| 桂东县| 荔波县| 贵阳市| 绥阳县| 宁化县| 五原县| 甘德县| 绵竹市| 汝南县|