java enum 學習

 
關鍵字: java enum 在像C這樣強調數據結構的語言裏,枚舉是必不可少的一種數據類型。然而在java的早期版本中,是沒有一種叫做enum的獨立數據結構的。所以在以前的java版本中,我們經常使用interface來simulate一個enum
java 代碼
  1. public interface Color {   
  2.     static int RED  = 1;   
  3.     static int GREEN    = 2;   
  4.     static int BLUE = 3;   
  5. }  

雖然這種simulation比較麻煩,但在以前也還應付的過去。可是隨着java語言的發展,越來越多的呼聲要求把enum這種數據結構獨立出來,加入到java中。所以從java 1.5以後,就有了enum,這也是這篇blog要學習的topic。

學習的最好方式就是例子,先來一個:

java 代碼
  1. public class EnumDemo {   
  2.     private enum Color {red, blue, green}//there is not a ";"   
  3.        
  4.     public static void main(String[] args) {   
  5.         for(Color s : Color.values()) {   
  6.             //enum的values()返回一個數組,這裏就是Seasons[]   
  7.             System.out.println(s);   
  8.         }   
  9.     }   
  10. }  
console results
  1. red   
  2. blue   
  3. green  

注意事項已經在code中註釋出,還要說明一點的是,這個java文件編譯完成後不只有一個EnumDemo.class,還會有一個EnumDemo$Seasons.class,奇怪吧!

Another e.g.

java 代碼
  1. public class EnumDemo {   
  2.     private enum Color {red, blue, green}//there is not a ";"   
  3.        
  4.     public static void main(String[] args) {   
  5.         Color s = Color.blue;   
  6.            
  7.         switch (s) {   
  8.         case red://notice: Seasons.red will lead to compile error   
  9.             System.out.println("red case");   
  10.             break;   
  11.         case blue:   
  12.             System.out.println("blue case");   
  13.             break;   
  14.         case green:   
  15.             System.out.println("green case");   
  16.             break;   
  17.         default:   
  18.             break;   
  19.         }   
  20.     }   
  21. }  

這個例子要說明的就是case的情況。

就這麼多嗎,當然不是,我們的enum結構還可以定義自己的方法和屬性。

java 代碼
  1. public class EnumDemo {   
  2.     private enum Color {   
  3.         red, blue, green;//there is a ";"   
  4.            
  5.         //notic: enum's method should be "static"   
  6.         public static Color getColor(String s){   
  7.             if(s.equals("red flag")){   
  8.                 return red;   
  9.             } else if(s.equals("blue flag")){   
  10.                 return blue;   
  11.             } else {   
  12.                 return green;   
  13.             }   
  14.         }   
  15.     }//there is not ";"   
  16.        
  17.     public static void main(String[] args) {   
  18.         EnumDemo demo = new EnumDemo();   
  19.         System.out.println(demo.getFlagColor("red flag"));   
  20.     }   
  21.        
  22.     public Color getFlagColor(String string){   
  23.         return Color.getColor(string);   
  24.     }   
  25. }  
Ok,so much for enum. Isn't it simple?.




本文摘自IBM DW,如有轉載,請聲明!
枚舉類型入門
----用 Java 5.0 以類型安全的方式表示常量
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;
}

摘者注:下面這段話說明了上述編程的弊端(除了這裏所說的內容外,摘者認爲如果OldGrade類中只有這些常量的話,應該將其定義爲接口,這樣可以防止錯誤的實例化這個類)
然後您就可以讓類接受像 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 {
priv
ate Grade grade;
public void assignGrade(Grade grade) {
    this.grade = grade;
}
public Grade getGrade() {
    return grade;
}
public static void main(String[] args) {
Student std = new Student();
              std.assignGrade(Grade.A);
              System.out.println(std.getGrade());
}
}
用以前定義過的類型建立一個新的枚舉( grade )之後,您就可以像使用其他成員變量一樣使用它了。當然,枚舉只能分配枚舉值中的一個(例如, A 、 C 或 INCOMPLETE )。而且,在 assignGrade() 中是沒有進行錯誤檢測的代碼,也沒有考慮邊界情況,請注意這是如何做到。
(摘者注:摘者將上面的使用枚舉類型的類代碼進行了修改,將一些設置學生姓名等內容刪除,並將構造函數也刪除,只留下對說明枚舉類型有作用的代碼,並添加 了main方法來進行grade的設置和獲取並打印,程序執行結果爲A,由於使用枚舉類型在方法assignGrade的參數中定義的是枚舉類型,而不是 java的int類型,所以傳遞的參數必須是枚舉類型參數,而枚舉類型參數中定義的值都是合法的)。
使用枚舉值
迄今爲止,您所看到的示例都相當簡單,但是枚舉類型提供的東西遠不止這些。您可以逐個遍歷枚舉值,也可以在 switch 語句中使用枚舉值,枚舉是非常有價值的。
遍歷枚舉值
下面我們用一個示例顯示如何遍歷枚舉類型的值。清單 4 所示的這項技術,適用於調試、快速打印任務以及把枚舉加載到集合(我很快將談到)中的工具:
清單 4. 遍歷枚舉值
將上面的main方法修改爲如下內容:
public static void main(String[] args) {
                            for (Grade g : Grade.values()) {
                                          System.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 static void main(String[] args) {
                            Student std = new Student();
                            std.assignGrade(Grade.INCOMPLETE);
                            switch (std.getGrade()) {
                            case A:
                                          System.out.println("excelled with a grade of A");
                                          break;
                            case B: // fall through to C
                            case C:
                                          System.out.println("passed with a grade of "
                                                                      + std.getGrade().toString());
                                          break;
                            case D: // fall through to F
                            case F:
                                          System.out.println("failed with a grade of "+ std.getGrade().toString());
                                          break;
                            case INCOMPLETE:
                                          System.out.println("did not complete the class.");
                                          break;
                            }
              }
在這裏,枚舉值被傳遞到 switch 語句中(請記住, getGrade() 是作爲 Grade 的實例返回的),而每個 case 子句將處理一個特定的值。該值在提供時沒有枚舉前綴,這意味着不用將代碼寫成 case Grade.A ,只需將其寫成 case A 即可。如果您不這麼做,編譯器不會接受有前綴的值。
現在,您應該已經瞭解使用 switch 語句時的基本語法,但是還有一些事情您需要知道。
在使用 switch 之前進行計劃
正如您所期待的,在使用枚舉和 switch 時,您可以使用 default 語句。清單 7 顯示了這個用法:
清單 7. 添加一個 default 塊
public static void main(String[] args) {
               Student std = new Student();
                            std.assignGrade(Grade.INCOMPLETE);
                            switch (std.getGrade()) {
                            case A:
                                          System.out.println("excelled with a grade of A");
                                          break;
                            case B: // fall through to C
                            case C:
                                          System.out.println("passed with a grade of "+ std.getGrade().toString());
                                          break;
                            case D: // fall through to F
                            case F:
                                          System.out.println("failed with a grade of "
                                                                      + std.getGrade().toString());
                                          break;
                            case INCOMPLETE:
System.out.println("did not complete the class.");
                                          break;
                            }
             default:
           System.out.println("has a grade of "+std.getGrade().toString());
      break;
              }

研究以上代碼可以看出,任何沒有被 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 所示:

清單 11. 清單 10 的輸出
For status INITIALIZING,message is: Initializing Ant...
For status COMPILING, message is: Compiling Java classes...
For status COPYING, message is: Copying files...
For status JARRING, message is: JARring up files...
For status ZIPPING, message is: ZIPping up files...
For status DONE, message is: Build complete.
For status ERROR, message is: Error occurred.
更進一步
枚舉也可以與集合結合使用,而且非常像新的EnumMap構造,Tiger 提供了一套新的EnumSet實現,允許您使用位操作符。另外,可以爲枚舉添加方法,用它們實現接口,定義叫作特定值的類的實體,在該實體中,特定的代碼 被附加到枚舉的具體值上。這些特性超出了本文的範圍,但是在其他地方,有詳細介紹它們的文檔(請參閱參考資料)。
使用枚舉,但是不要濫用
學習任何新版語言的一個危險就是瘋狂使用新的語法結構。如果這樣做,那麼您的代碼就會突然之間有 80% 是泛型、標註和枚舉。所以,應當只在適合使用枚舉的地方纔使用它。那麼,枚舉在什麼地方適用呢?一條普遍規則是,任何使用常量的地方,例如目前用 switch代碼切換常量的地方。如果只有單獨一個值(例如,鞋的最大尺寸,或者籠子中能裝猴子的最大數目),則還是把這個任務留給常量吧。但是,如果定 義了一組值,而這些值中的任何一個都可以用於特定的數據類型,那麼將枚舉用在這個地方最適合不過。
摘者將Sun Java 5.0對於類型安全的Enum的說明拷貝放在下邊,以供參考:
In prior releases, the standard way represent an enumerated type was the int Enum pattern: public static final int SEASON_WINTER = 0;
public static final int SEASON_SPRING = 1;
public static final int SEASON_SUMMER = 2;
public static final int SEASON_FALL   = 3;
This pattern has many problems, such as:

Not typesafe - Since a season is just an int you can pass in any other int value where a season is required, or add two seasons together (which makes no sense).
No namespace - You must prefix constants of an int enum with a string (in this case SEASON_) to avoid collisions with other int enum types.
Brittleness - Because int enums are compile-time constants, they are compiled into clients that use them. If a new constant is added between two existing constants or the order is changed, clients must be recompiled. If they are not, they will still run, but their behavior will be undefined.
Printed values are uninformative - Because they are just ints, if you print one out all you get is a number, which tells you nothing about what it represents, or even what type it is.

It is possible to get around these problems by using the Typesafe Enum pattern (see Effective Java Item 21), but this pattern has its own problems: It is quite verbose, hence error prone, and its enum constants cannot be used in switch statements.
In Tiger, the Java™ programming language gets linguistic support for enumerated types. In their simplest form, these enums look just like their C, C++, and C# counterparts:
enum Season { WINTER, SPRING, SUMMER, FALL }
But appearances can be deceiving. Java programming language enums are far more powerful than their counterparts in other languages, which are little more than glorified integers. The new enum declaration defines a full-fledged class (dubbed an enum type). In addition to solving all the problems mentioned above,

it allows you to add arbitrary methods and fields to an enum type, to implement arbitrary interfaces, and more. Enum types provide high-quality implementations of all the Object methods. They are Comparable and Serializable, and the serial form is designed to withstand arbitrary changes in the enum type.
Here is an example of a playing card class built atop a couple of simple enum types. The Card class is immutable, and only one instance of each Card is created, so it need not override equals or hashCode:
import java.util.*;

public class Card {
    public enum Rank { DEUCE, THREE, FOUR, FIVE, SIX,
        SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE }

    public enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES }

    private final Rank rank;
    private final Suit suit;
    private Card(Rank rank, Suit suit) {
        this.rank = rank;
        this.suit = suit;
    }

    public Rank rank() { return rank; }
    public Suit suit() { return suit; }
    public String toString() { return rank + " of " + suit; }

    private static final List<Card> protoDeck = new ArrayList<Card>();

    // Initialize prototype deck
    static {
        for (Suit suit : Suit.values())
            for (Rank rank : Rank.values())
                protoDeck.add(new Card(rank, suit));
    }

    public static ArrayList<Card> newDeck() {
        return new ArrayList<Card>(protoDeck); // Return copy of prototype deck
    }
}
The toString method for Card takes advantage of the toString methods for Rank and Suit. Note that the Card class is short (about 25 lines of code). If the typesafe enums (Rank and Suit) had been built by hand, each of them would have been significantly longer than the entire Card class.
The (private) constructor of Card takes two parameters, a Rank and a Suit. If you accidentally invoke the constructor with the parameters reversed, the compiler will politely inform you of your error. Contrast this to the int enum pattern, in which the program would fail at run time.
Note that each enum type has a static values method that returns an array containing all of the values of the enum type in the order they are declared. This method is commonly used in combination with the for-each loop to iterate over the values of an enumerated type.
The following example is a simple program called Deal that exercises Card. It reads two numbers from the command line, representing the number of hands to deal and the number of cards per hand. Then it creates a new deck of cards, shuffles it, and deals and prints the requested hands.
import java.util.*;

public class Deal {
    public static void main(String args[]) {
        int numHands = Integer.parseInt(args[0]);
        int cardsPerHand = Integer.parseInt(args[1]);
        List<Card> deck = Card.newDeck();
        Collections.shuffle(deck);
        for (int i=0; i < numHands; i++)
            System.out.println(deal(deck, cardsPerHand));
    }

    public static ArrayList<Card> deal(List<Card> deck,

int n) {
         int deckSize = deck.size();
         List<Card> handView = deck.subList(deckSize-n, deckSize);
         ArrayList<Card> hand = new ArrayList<Card>(handView);
         handView.clear();
         return hand;
     }
}

$ java Deal 4 5
[FOUR of HEARTS, NINE of DIAMONDS, QUEEN of SPADES, ACE of SPADES, NINE of SPADES]
[DEUCE of HEARTS, EIGHT of SPADES, JACK of DIAMONDS, TEN of CLUBS, SEVEN of SPADES]
[FIVE of HEARTS, FOUR of DIAMONDS, SIX of DIAMONDS, NINE of CLUBS, JACK of CLUBS]
[SEVEN of HEARTS, SIX of CLUBS, DEUCE of DIAMONDS, THREE of SPADES, EIGHT of CLUBS]
Suppose you want to add data and behavior to an enum. For example consider the planets of the solar system. Each planet knows its mass and radius, and can calculate its surface gravity and the weight of an object on the planet. Here is how it looks:
public enum Planet {
    MERCURY (3.303e+23, 2.4397e6),
    VENUS   (4.869e+24, 6.0518e6),
    EARTH   (5.976e+24, 6.37814e6),
    MARS    (6.421e+23, 3.3972e6),
    JUPITER (1.9e+27,   7.1492e7),
    SATURN (5.688e+26, 6.0268e7),
    URANUS (8.686e+25, 2.5559e7),
    NEPTUNE (1.024e+26, 2.4746e7),
    PLUTO   (1.27e+22, 1.137e6);

    private final double mass;   // in kilograms
    private final double radius; // in meters
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }

private double mass()   { return mass; }
    private double radius() { return radius; }

    // universal gravitational constant (m3 kg-1 s-2)
    public static final double G = 6.67300E-11;

    double surfaceGravity() {
        return G * mass / (radius * radius);
    }
    double surfaceWeight(double otherMass) {
        return otherMass * surfaceGravity();
    }
}
The enum type Planet contains a constructor, and each enum constant is declared with parameters to be passed to the constructor when it is created.
Here is a sample program that takes your weight on earth (in any unit) and calculates and prints your weight on all of the planets (in the same unit):
    public static void main(String[] args) {
        double earthWeight = Double.parseDouble(args[0]);
        double mass = earthWeight/EARTH.surfaceGravity();
        for (Planet p : Planet.values())
           System.out.printf("Your weight on %s is %f%n",
                             p, p.surfaceWeight(mass));
    }

$ java Planet 175
Your weight on MERCURY is 66.107583
Your weight on VENUS is 158.374842
Your weight on EARTH is 175.000000
Your weight on MARS is 66.279007
Your weight on JUPITER is 442.847567
Your weight on SATURN is 186.552719
Your weight on URANUS is 158.397260
Your weight on NEPTUNE is 199.207413
Your weight on PLUTO is 11.703031
The idea of adding behavior to enum consta

nts can be taken one step further. You can give each enum constant a different behavior for some method. One way to do this by switching on the enumeration constant. Here is an example with an enum whose constants represent the four basic arithmetic operations, and whose eval method performs the operation:
public enum Operation {
    PLUS, MINUS, TIMES, DIVIDE;

    // Do arithmetic op represented by this constant
    double eval(double x, double y){
        switch(this) {
            case PLUS:   return x + y;
            case MINUS: return x - y;
            case TIMES: return x * y;
            case DIVIDE: return x / y;
        }
        throw new AssertionError("Unknown op: " + this);
    }
}
This works fine, but it will not compile without the throw statement, which is not terribly pretty. Worse, you must remember to add a new case to the switch statement each time you add a new constant to Operation. If you forget, the eval method with fail, executing the aforementioned throw statement
There is another way give each enum constant a different behavior for some method that avoids these problems. You can declare the method abstract in the enum type and override it with a concrete method in each constant. Such methods are known as constant-specific methods. Here is the previous example redone using this technique:
public enum Operation {
PLUS   { double eval(double x, double y) { return x + y; } },
MINUS { double eval(double x, double y) { return x - y; } },
TIMES { double eval(double x, double y) { return x * y; } },
DIVIDE { double eval(double x, double y) { return x / y; } }// Do arithmetic op represented by this constant
abstract double eval(double x, double y);
}
Here is a sample program that exercises the Operation class. It takes two operands from the command line, iterates over all the operations, and for each operation, performs the operation and prints the resulting equation:
    public static void main(String args[]) {
        double x = Double.parseDouble(args[0]);
        double y = Double.parseDouble(args[1]);
        for (Operation op : Operation.values())
            System.out.printf("%f %s %f = %f%n", x, op, y, op.eval(x, y));
    }

$ java Operation 4 2
4.000000 PLUS 2.000000 = 6.000000
4.000000 MINUS 2.000000 = 2.000000
4.000000 TIMES 2.000000 = 8.000000
4.000000 DIVIDE 2.000000 = 2.000000
Constant-specific methods are reasonably sophisticated, and many programmers will never need to use them, but it is nice to know that they are there if you need them.
Two classes have been added to java.util in support of enums: special-purpose Set and Map implementations called EnumSet and EnumMap. EnumSet is a high-performance Set implementation for enums. All of the members of an enum set must be of the same enum type. Internally, it is represented by a bit-vector, typically a single long. Enum sets support iteration over ranges of enum types. For example given the following enum declaration:
    enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }
you can iterate over the weekdays. The EnumSet class provides a static factory that makes it easy:
    for (Day d : EnumSet.range(Day.MONDAY, Day.FRIDAY))
        System.out.println(d);
Enum sets also provide a rich,typesafe replacement for traditional bit flags:
    EnumSet.of(Style.BOLD, Style.ITALIC)
Similarly, EnumMap is a high-performance Map implementation for use with enum keys, internally implemented as an array. Enum maps combine the richness and safety of the Map interface with speed approaching that of an array. If you want to map an enum to a value, you should always use an EnumMap in preference to an array.
The Card class, above, contains a static factory that returns a deck, but there is no way to get an individual card from its rank and suit. Merely exposing the constructor would destroy the singleton property (that only a single instance of each card is allowed to exist). Here is how to write a static factory that preserves the singleton property, using a nested EnumMap:
private static Map<Suit, Map<Rank, Card>> table =
    new EnumMap<Suit, Map<Rank, Card>>(Suit.class);
static {
    for (Suit suit : Suit.values()) {
        Map<Rank, Card> suitTable = new EnumMap<Rank, Card>(Rank.class);
        for (Rank rank : Rank.values())
            suitTable.put(rank, new Card(rank, suit));
        table.put(suit, suitTable);
    }
}

public static Card valueOf(Rank rank, Suit suit) {
    return table.get(suit).get(rank);
}
The EnumMap (table) maps each suit to an EnumMap that maps each rank to a card. The lookup performed by the valueOf method is internally implemented as two array accesses, but the code is much clearer and safer. In order to preserve the singleton property, it is imperative that the constructor invocation in the prototype deck initialization in Card be replaced by a call to the new static factory:
    // Initialize prototype deck
    static {


       for (Suit suit : Suit.values())
            for (Rank rank : Rank.values())
                protoDeck.add(Card.valueOf(rank, suit));
    }
It is also imperative that the initialization of table be placed above the initialization of protoDeck, as the latter depends on the former.
So when should you use enums? Any time you need a fixed set of constants. That includes natural enumerated types (like the planets, days of the week, and suits in a card deck) as well as other sets where you know all possible values at compile time, such as choices on a menu, rounding modes, command line flags, and the like. It is not necessary that the set of constants in an enum type stay fixed for all time. The feature was specifically designed to allow for binary compatible evolution of enum types.


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章