关于ENUM

来源:互联网 发布:淘宝网毛线披肩有吗 编辑:程序博客网 时间:2024/05/16 04:51
Java

Enums

Language Contents

 


文章来源  http://java.sun.com/j2se/1.5.0/docs/guide/language/enums.html

In prior releases, the standard way to represent an enumerated type was the int Enum pattern:

// int Enum Pattern - has severe problems!
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-timeconstants, they are compiled into clients that use them. If a newconstant is added between two existing constants or the order ischanged, clients must be recompiled. If they are not, they will stillrun, but their behavior will be undefined.
  • Printed values are uninformative - Because they arejust ints, if you print one out all you get is a number, which tellsyou 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 EffectiveJava Item 21),but this pattern has its own problems: It is quite verbose, hence errorprone, and its enum constants cannot be used in switch statements.

In 5.0, the Java™ programming language gets linguistic support forenumerated 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 farmore powerful than their counterparts in other languages, which are littlemore than glorified integers. The new enum declaration defines afull-fledged class (dubbed an enum type). In addition to solving allthe problems mentioned above, it allows you to add arbitrary methods andfields to an enum type, to implement arbitrary interfaces, and more. Enumtypes provide high-quality implementations of all the Object methods.They are Comparable and Serializable, and the serial form isdesigned to withstand arbitrary changes in the enum type.

Here is an example of a playing card class built atop acouple of simple enum types. The Card class is immutable, and onlyone instance of each Card is created, so it need not overrideequals 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 forRank and Suit. Note that the Card class isshort (about 25 lines of code). If the typesafe enums (Rank andSuit) 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, aRank and a Suit. If you accidentally invoke the constructorwith the parameters reversed, the compiler will politely inform you of yourerror. Contrast this to the int enum pattern, in which theprogram would fail at run time.

Note that each enum type has a static values method that returnsan array containing all of the values of the enum type in the order theyare declared. This method is commonly used in combination with the for-each loop to iterate over the values of anenumerated type.

The following example is a simple program called Deal thatexercises 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 therequested 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 exampleconsider the planets of the solar system.Each planet knows its mass and radius, and can calculate its surfacegravity 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;
}
public double mass() { return mass; }
public double radius() { return radius; }

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

public double surfaceGravity() {
return G * mass / (radius * radius);
}
public double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();
}
}

The enum type Planet contains a constructor, and each enumconstant is declared with parameters to be passed to the constructorwhen it is created.

Here is a sample program that takes your weight onearth (in any unit) and calculates and prints your weight on all of theplanets (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 constants can be taken one stepfurther. You can give each enum constant a different behaviorfor some method. One way to do this by switching on the enumeration constant.Here is an example with an enum whose constants represent the fourbasic arithmetic operations, and whose eval method performs theoperation:

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 caseto the switch statement each time you add a new constant toOperation. If you forget, the eval method with fail,executing the aforementioned throw statement

There is another way give each enum constant a different behaviorfor some method that avoids these problems. You can declare the methodabstract in the enum type and override it with a concrete method in eachconstant. 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 Operationclass. It takes two operands from the command line, iterates over allthe operations, and for each operation, performs the operation andprints 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 manyprogrammers will never need to use them, but it is nice to know thatthey are there if you need them.

Two classes have been added to java.util in support of enums:special-purpose Set and Map implementations calledEnumSetand EnumMap.EnumSet is a high-performanceSet implementation for enums. All of the members of an enum set must be of the same enum type. Internally, it is represented by abit-vector, typically a single long. Enum sets support iterationover ranges of enum types. For example given the following enumdeclaration:

    enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }

you can iterate over the weekdays. The EnumSet classprovides 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 traditionalbit flags:

    EnumSet.of(Style.BOLD, Style.ITALIC)

Similarly, EnumMap is a high-performance Mapimplementation for use with enum keys, internally implemented as an array.Enum maps combine the richness and safety of the Map interfacewith speed approaching that of an array. If you want to map an enum to avalue, you should always use an EnumMap in preference to an array.

The Card class, above, contains a staticfactory that returns a deck, but there is no way to get an individual cardfrom its rank and suit. Merely exposing the constructor would destroy thesingleton property (that only a single instance of each card is allowed toexist). Here is how to write a static factory that preserves the singletonproperty, 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 thevalueOf method is internally implemented as two array accesses,but the code is much clearer and safer. In order to preserve thesingleton property, it is imperative that the constructor invocation in theprototype deck initialization in Card be replaced by a call tothe 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 placedabove the initialization of protoDeck, as the latter depends on theformer.

So when should you use enums? Any time you need a fixed set ofconstants. That includes natural enumerated types (like the planets,days of the week, and suits in a card deck) as well as other setswhere you know all possible values at compile time, such as choices ona menu, rounding modes, command line flags, and the like. It isnot necessary that the set of constants in an enum type stayfixed for all time. The feature was specifically designed to allowfor binary compatible evolution of enum types.


Copyright © 2004 Sun Microsystems, Inc. All Rights Reserved. Sun

Java Software