Introduction to Enumeration (enum) (JDK 5)
Example 1: Scissor-Paper-Stone
Suppose that we are writing a Scissor-Paper-Stone game. We could use three arbitrary integers (e.g., 0, 1, 2; or 88, 128, 168), three strings ("Scissor", "Paper", "Stone"), or three characters ('s'
, 'p'
, 't'
) to represent the three hand-signs. The main drawback is we need to check the other infeasible values (e.g. 3, "Rock", 'q'
, etc.) in our program to ensure correctness.
A better approach is to define our own list of permissible values in a construct called enumeration (or enum
), introduced in JDK 5. The syntax is as follows:
enum { ITEM1, ITEM2, ...; }
For example,
public enum HandSign { // Save as "HandSign.java" SCISSOR, PAPER, STONE; }
An enum
is a special class
. Hence, you need to save the "public
" enum
as "EnumName
.java
".
An enumeration is a special type, which provides a type-safe implementation of constants in your program. In other words, we can declare a variable of the type HandSign
, which takes values of either HandSign.SCISSOR
, HandSign.PAPER
, or HandSign.STONE
, but NOTHING ELSE. For example,
public class EnumHandSignTest { public static void main(String[] args) { HandSign playerMove, computerMove; // Declare variables of the enum type HandSign playerMove = HandSign.SCISSOR; // Assign values into enum variables computerMove = HandSign.PAPER; System.out.println(playerMove); //SCISSOR System.out.println(computerMove); //PAPER //playerMove = 0; //compilation error: incompatible types: int cannot be converted to HandSign } }
Example: Below is a Scissor-Paper-Stone game using an enumeration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
import java.util.Random; import java.util.Scanner; /* * Define an enumeration called HandSign, with 3 elements, referred to as: * HandSign.SCISSOR, HandSign.PAPER, HandSign.STONE. */ enum HandSign { // default package access SCISSOR, PAPER, STONE; } /* * A game of scissor-paper-stone. */ public class ScissorPaperStone { public static void main(String[] args) { Random random = new Random(); // Create a random number generator boolean gameOver = false; HandSign playerMove = HandSign.SCISSOR; HandSign computerMove; int numTrials = 0; int numComputerWon = 0, numPlayerWon = 0, numTie = 0; Scanner in = new Scanner(System.in); System.out.println("Let us begin..."); while (!gameOver) { System.out.println("Scissor-Paper-Stone"); // Player move // Use a do-while loop to handle invalid input boolean validInput; do { System.out.print(" Show me your sign (Enter s for scissor, p for paper, t for stone, q to quit): "); char inChar = in.next().toLowerCase().charAt(0); // Convert to lowercase and extract first char validInput = true; // assume valid unless otherwise switch (inChar) { case 'q': gameOver = true; break; case 's': playerMove = HandSign.SCISSOR; break; case 'p': playerMove = HandSign.PAPER; break; case 't': playerMove = HandSign.STONE; break; default: System.out.println(" Invalid input, try again..."); validInput = false; } } while (!validInput); if (!gameOver) { // Computer Move // Generate a random int 0 to 2, which happens to corresponds to // the ordinal of the elements of the enum int rand = random.nextInt(3); // random int between 0 and 2 computerMove = HandSign.values()[rand]; // map enum to array System.out.println(" My sign is: " + computerMove); // Check result if (computerMove == playerMove) { System.out.println(" Tie!"); ++numTie; } else if (computerMove == HandSign.SCISSOR && playerMove == HandSign.PAPER) { System.out.println(" Scissor cuts paper, I won!"); ++numComputerWon; } else if (computerMove == HandSign.PAPER && playerMove == HandSign.STONE) { System.out.println(" Paper wraps stone, I won!"); ++numComputerWon; } else if (computerMove == HandSign.STONE && playerMove == HandSign.SCISSOR) { System.out.println(" Stone breaks scissor, I won!"); ++numComputerWon; } else { System.out.println(" You won!"); ++numPlayerWon; } ++numTrials; } } // Print statistics System.out.println("\nNumber of trials: " + numTrials); System.out.printf("I won %d(%.2f%%). You won %d(%.2f%%).%n", numComputerWon, 100.0*numComputerWon/numTrials, numPlayerWon, 100.0*numPlayerWon/numTrials); System.out.println("Bye! "); } } |
Let us begin...
Scissor-Paper-Stone
Show me your sign (Enter s for scissor, p for paper, t for stone, q to quit): s
My sign is: PAPER
You won!
Scissor-Paper-Stone
Show me your sign (Enter s for scissor, p for paper, t for stone, q to quit): p
My sign is: SCISSOR
Scissor cuts paper, I won!
Scissor-Paper-Stone
Show me your sign (Enter s for scissor, p for paper, t for stone, q to quit): t
My sign is: PAPER
Paper wraps stone, I won!
Scissor-Paper-Stone
Show me your sign (Enter s for scissor, p for paper, t for stone, q to quit): q
Number of trials: 3
I won 2(66.67%). You won 1(33.33%).
Bye!
Note that I used the utility Random
to generate a random integer between 0 and 2, as follows:
import java.util.Random; // Needed to use Random // In main() Random random = new Random(); // Create a random number generator int rand = random.nextInt(3); // Each call returns a random int between 0 (inclusive) and 3 (exclusive)
The static
method EnumName.values()
returns an array of all the elements. The random number (0-2) generated corresponds to the index of the elements in the array (or the ordinal of the element in the enumeration).
EnumName.values()
An enum
has a static
method called values()
that returns an array of all the enum
constants, in the order they were defined. For example,
System.out.println(java.util.Arrays.toString(HandSign.values())); // returns an array //[SCISSOR, PAPER, STONE] for (HandSign sign : HandSign.values()) { System.out.println(sign); } //SCISSOR //PAPER //STONE
.ordinal()
public final int ordinal() // Returns the ordinal of this enumeration constant
You can use .ordinal()
to get the ordinal of this enum element. The ordinal is the position in its enum declaration, where the initial constant is assigned an ordinal of zero. For example,
for (HandSign sign : HandSign.values()) { System.out.println(sign + ":" + sign.ordinal()); } //SCISSOR:0 //PAPER:1 //STONE:2
Example 2: Deck of Card
A card's suit can only be diamond, club, heart or spade. In other words, it has a limited set of values. Before the introduction of enum
in JDK 5, we usually have to use an int
variable to hold these values. For example,
class CardSuit {
public static final int DIAMOND 0;
public static final int CLUB 1;
public static final int HEART 2;
public static final int SPADE 3;
......
}
class Card {
private int suit; // CardSuit.DIAMOND, CardSuit.CLUB, CardSuit.HEART, CardSuit.SPADE
}
The drawbacks are:
- It is not type-safe. You can assign any
int
value (e.g., 128) into theint
variablesuit
. - No namespace: You must prefix the constants by the class name
CardSuit
. - Brittleness: new constants will break the existing codes.
- Printed values are uninformative: printed value of 0, 1, 2 and 3 are not very meaningful.
JDK 5 introduces a new enum
type (in addition to the existing top-level constructs class
and interface
) along with a new keyword enum
. For example, we could define:
enum Suit { // default package access
DIAMOND, CLUB, HEART, SPADE;
}
An enum
can be used to define a set of enum
constants. The constants are implicitly static final
, which cannot be modified. You could refer to these constants just like any static
constants, e.g., Suit.SPADE
, Suit.HEART
, etc. enum
is type-safe. It has its own namespace. enum
works with switch-case statement (just like the existing int
and char
).
For example,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
import java.util.List; import java.util.ArrayList; import java.util.Collections; enum Suit { DIAMOND, CLUB, HEART, SPADE; } enum Rank { ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING; } class Card { // A card private Suit suit; private Rank rank; Card(Suit suit, Rank rank) { // constructor this.suit = suit; this.rank = rank; } public Suit getSuit() { return suit; } public Rank getRank() { return rank; } public int getValue() { return rank.ordinal() + 1; } // 1 to 13 public String toString() { return suit + "-" + rank; } } class CardDeck { // A deck of card List<Card> deck; CardDeck() { // constructor deck = new ArrayList<>(); // JDK 7 type inference for (Suit suit : Suit.values()) { for (Rank rank : Rank.values()) { deck.add(new Card(suit, rank)); } } } public void print() { System.out.println(deck); } public void shuffle() { // use Collections' static method to shuffle the List in place Collections.shuffle(deck); } } public class CardTest { public static void main(String[] args) { CardDeck deck = new CardDeck(); deck.print(); deck.shuffle(); deck.print(); } } |
More on Enumeration
Constructor, Member Variables and Methods
An enum
is a reference type (just like a class, interface and array), which holds a reference to memory in the heap. It is implicitly final
, because the constants should not be changed. It can include other component of a traditional class, such as constructors, member variables and methods. (This is where Java's enum
is more powerful than C/C++'s counterpart). Each enum
constant can be declared with parameters to be passed to the constructor when it is created. For example,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
enum TrafficLight { RED(30), AMBER(10), GREEN(30); // Named constants private final int seconds; // Private variable TrafficLight(int seconds) { // Constructor this.seconds = seconds; } int getSeconds() { // Getter return seconds; } } public class TrafficLightTest { public static void main(String[] args) { for (TrafficLight light : TrafficLight.values()) { System.out.printf("%s: %d seconds\n", light, light.getSeconds()); } } } |
Three instances of enum
type TrafficLight
were generated via values()
. The instances are created by calling the constructor with the actual argument, when they are first referenced. You are not allowed to construct a new instance of enum
using new
operator, because enum
keeps a fixed list of constants. enum
's instances could have its own instance variable (int seconds
) and method (getSeconds()
).
Enum with with Image and Sound
See "Tic Tac Toe" example "A Graphical Tic-Tac-Toe with Sound Effect and Images".
Enum with abstract method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
enum TLight { // Each instance provides its implementation to abstract method RED(30) { public TLight next() { return GREEN; } }, AMBER(10) { public TLight next() { return RED; } }, GREEN(30) { public TLight next() { return AMBER; } }; public abstract TLight next(); // An abstract method private final int seconds; // Private variable TLight(int seconds) { // Constructor this.seconds = seconds; } int getSeconds() { // Getter return seconds; } } public class TLightTest { public static void main(String[] args) { for (TLight light : TLight.values()) { System.out.printf("%s: %d seconds, next is %s\n", light, light.getSeconds(), light.next()); } } } |
Each of the instances of enum
could have its own behaviors. To do this, you can define an abstract
method in the enum
, where each of its instances provides its own implementation.
Another Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
enum Day { MONDAY(1) { public Day next() { return TUESDAY; } // each instance provides its implementation to abstract method }, TUESDAY(2) { public Day next() { return WEDNESDAY; } }, WEDNESDAY(3) { public Day next() { return THURSDAY; } }, THURSDAY(4) { public Day next() { return FRIDAY; } }, FRIDAY(5) { public Day next() { return SATURDAY; } }, SATURDAY(6) { public Day next() { return SUNDAY; } }, SUNDAY(7) { public Day next() { return MONDAY; } }; public abstract Day next(); private final int dayNumber; Day(int dayNumber) { // constructor this.dayNumber = dayNumber; } int getDayNumber() { return dayNumber; } } public class DayTest { public static void main(String[] args) { for (Day day : Day.values()) { System.out.printf("%s (%d), next is %s\n", day, day.getDayNumber(), day.next()); } } } |
java.util.EnumSet & java.util.EnumMap
Two classes have been added to java.util
to support enum
: EnumSet
and EnumMap
. They are high performance implementation of the Set
and Map
interfaces respectively.
[TODO]
Miscellaneous
How to get the number of elements in an enum?
Use EnumName.values()
to get an array, then .length
, e.g., Suit.values().length
.
How to get the order of an element?
Use .ordinal()
for an int
order starting from 0
.
Summary
So when should you use enum
s? Any time you need a fixed set of constants, whose values are known at compile-time. That includes natural enumerated types (like the 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, command line flags, and so on. It is not necessary that the set of constants in an enum type stays fixed for all time. In most of the situations, you can add new constants to an enum without breaking the existing codes.
Properties:
- Enums are type-safe!
- Enums provide their namespace.
- Whenever an
enum
is defined, a class that extendsjava.lang.Enum
is created. Hence, enum cannot extend anotherclass
orenum
. The compiler also create an instance of the class for each constants defined inside theenum
. Thejava.lang.Enum
has these methods:public final String name(); // Returns the name of this enum constant, exactly as declared in its enum declaration. // You could also override the toString() to provide a more user-friendly description. public String toString(); // Returns the name of this enum constant, as contained in the declaration. // This method may be overridden. public final int ordinal(); // Returns the ordinal of this enumeration constant.
- All constants defined in an enum are
public static final
. Since they arestatic
, they can be accessed viaEnumName.instanceName
. - You do not instantiate an
enum
, but rely the constants defined. - Enums can be used in a switch-case statement, just like an
int
.
(Advanced) Behind the Scene
Consider the following enum
:
public enum Size { BIG, MEDIUM, SMALL; }
Let's decompile this enum
and see what the compiler generates:
> javac Size.java > javap -c Size.class Compiled from "Size.java" public final class Size extends java.lang.Enum{ public static final Size BIG; public static final Size MEDIUM; public static final Size SMALL; public static Size[] values(); ...... public static Size valueOf(java.lang.String); ...... static {}; ......
Notes:
- A
enum
extends fromjava.lang.Enum
. - All of the possible values of a
enum
are defined aspublic static final
variables.