Enumerations are special classes that contain
a set of related constants, allowing you to have more
clarity and consistency in your applications. It's
much easier to have an enumeration for the month
numbers for each month of the year, than it is to
keep track of a set of constants and their values.
Furthermore, in Java you can
do a lot more with enumerations: for example, you can
assign multiple values to a single constant, and you can
add methods to an enumeration. This makes enumerations
much more flexible.
In this lesson you'll learn all about using
enumerations, adding extra values to the enumeration
constants, adding methods to enumerations, and so much
more.
What are Enumerations?
Enumerations (or
Enums, for short) are special
classes that contain a set of related constants.
Unlike other languages like C++, Java Enums can also contain
other methods and code, as we'll see later.
For example, perhaps I have a company that teaches people how to
make their own chocolates, and I need an application that allows
users to view course information and sign up for courses. I
might have an enumeration called Course (enum classes
identifiers have the same rules and standards as regular class
identifiers). Inside that Course
enum, I might have constants for each of the course codes offered,
and each constant contains the title of the course. For example
Course.CHOC101 has a value of
"Introduction to Chocolate",
Course.CHOC102 has a value of
"Tempering Chocolate", and
Course.CHOC103 has a value of
"Basic Chocolate Confections".
If I want to store a specific course in a variable,
I would use a statement like
Course yum = Course.CHOC101;
Note that the data type of the yum
variable is of
type Course (the name of the enum class). You can't
put anything else into a Course variable except
a valid Course enum. You can't make up a constant that
isn't there, such as Course.FOO101 - you can only use
those constants that are defined in the enumeration class.
Why use Enumerations?
In past programs, you've probably created sets of constants
to make your program easier to code and more readable.
For example, maybe you've created a set of constants for
the numbers of the days of the week:
public class EnumsExample {
public static final int SUNDAY = 0;
public static final int MONDAY = 1;
public static final int TUESDAY = 2;
public static final int WEDNESDAY = 3;
public static final int THURSDAY = 4;
public static final int FRIDAY = 5;
public static final int SATURDAY = 6;
public static void main(String[] args) {
int dayOfWeek = 3;
switch(dayOfWeek) {
case SUNDAY:
System.out.println("Sunday");
break;
case MONDAY:
System.out.println("Monday");
break;
case TUESDAY:
System.out.println("Tuesday");
break;
case WEDNESDAY:
System.out.println("Wednesday");
break;
case THURSDAY:
System.out.println("Thursday");
break;
case FRIDAY:
System.out.println("Friday");
break;
case SATURDAY:
System.out.println("Saturday");
break;
default:
System.out.println("Invalid day of week number.");
}
}
}
This program works fine, however there are a couple of
problems with it:
The variable dayOfWeek can be assigned any
integer value. For example, the statement int dayOfWeek = 99;
would not cause an error. Instead, the number would simply
be considered invalid and will be caught by the default:
clause of the switch statement.
Printing a constant's value is meaningless: printing the
value of FRIDAY simply prints the integer 5.
It would be better if it printed something meaningful
like "Friday".
The constants are not reusable. You'd have to add them
to some kind of utility class in a library so you could use
them in other programs.
Creating an enumeration for the days of the week
solves these
issues and also adds a number of other conveniences
that will
make your code easier to use and read, less work,
and more efficient.
Defining Enumerations
The most basic enumeration is easy to create and define.
You simply create an Enum class and add
the constants:
public enum DayOfWeek {
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY; // semi-colon is optional if there's no other code
}
Note that this is just like a regular class except that we
use "public enum" instead of "public class".
The name of the enumeration class must follow the same
standards and naming conventions for other classes
(i.e. starts with an upper-case
letter, no spaces, use camel-casing, etc).
Inside the enum block (inside the opening and closing braces),
you list the constants separated by a comma. A semi-colon
after the last constant is optional as long as you have
no other code inside your enum class.
The DayOfWeek constants SUNDAY,
MONDAY, TUESDAY, etc. don't exactly
have a numeric value, but they do have an
ordinal. An
ordinal is the numeric index for a constant starting with
0 and ending with the number of constants - 1.
So in the DayOfWeek example, DayOfWeek.SUNDAY
has an ordinal of 0 and DayOfWeek.SATURDAY
has an ordinal of 6.
If you add or remove constants, this changes the ordinals.
For example, if you remove DayOfWeek.SUNDAY,
then DayOfWeek.MONDAY has an
ordinal of 0 and DayOfWeek.SATURDAY has an
ordinal of 5.
Exercises
1. Create an enum called Suit that contains constants for the four
suits in a standard deck of cards (Clubs, Diamonds, Hearts, and Spades).
2. Create an enum called Rank that contains constants for
each of the 13 ranks of cards found in a standard deck of cards.
For example: TWO, FIVE, JACK, KING, QUEEN, ACE.
3. Rewrite the main() program in the first example that
uses a switch statement to display the names of the days of the week:
get rid of the constants and use the DayOfWeek enum instead.
1. Suit enum:
public enum Suit {
CLUBS,
DIAMONDS,
HEARTS,
SPADES
}
(You could put ACE before TWO instead of after KING, if you wish)
3. Day of Week program (you can actually shorten this even more if
you're not fussy about how the day of the week is displayed):
public static void main(String[] args) {
DayOfWeek dayOfWeek = DayOfWeek.WEDNESDAY;
switch(dayOfWeek) {
case SUNDAY: // you don't have to day DayOfWeek.SUNDAY
System.out.println("Sunday");
break;
case MONDAY:
System.out.println("Monday");
break;
case TUESDAY:
System.out.println("Tuesday");
break;
case WEDNESDAY:
System.out.println("Wednesday");
break;
case THURSDAY:
System.out.println("Thursday");
break;
case FRIDAY:
System.out.println("Friday");
break;
case SATURDAY:
System.out.println("Saturday");
break;
default:
System.out.println("Invalid day of week number.");
}
Using Enumerations
Other than the ordinal value, the constant
DayOfWeek.SUNDAY has a value of SUNDAY.
To understand, you can add the DayOfWeek enum
class to a new project and add the following in the main
class's main() method:
public static void main(String[] args) {
DayOfWeek d = DayOfWeek.SUNDAY;
System.out.println(d);
System.out.println(d.ordinal());
System.out.println(d.name());
// you can do the same code without a variable:
System.out.println(DayOfWeek.FRIDAY);
System.out.println(DayOfWeek.FRIDAY.ordinal());
System.out.println(DayOfWeek.FRIDAY.name());
}
Notice that printing the actual enum constant simply
prints the constant's name. This is also the same value
returned by the enum's name() method.
Where do the name() and ordinal()
methods come from? All enums
you create in Java automatically extend the
java.lang.Enum class. If you examine the Enum
class documentation, you'll see that it includes several methods that
are inherited by your enum class. These include:
name(): (instance method)
returns the name of this enum constant as a String. You
probably won't use this often since you'll likely give your enum a
toString() override that returns something more useful.
ordinal(): (instance method)
returns the ordinal or index of this enum. All enum
constants are assigned an ordinal or index starting at 0 - the first
constant defined is 0, the second constant defined is 1, etc. and
the last constant defined is assigned the ordinal (# of constants) - 1.
toString(): (instance method)
this method from the Object class is overridden in
the Enum class: it returns the enum constant name as a String. You
can override this in your own enum if you wanted to have a different
String value for the enum constants.
valueOf(String str): (static method)
returns an enum constant that has the
same name of the value in the str parameter. For example,
Course.valueOf("CHOC101") would return Course.CHOC101.
If the str parameter's value doesn't match
any constant name in the enum, an IllegalArgumentException
is thrown. This method comes in handy,
for example if your user is selecting or entering a String value
for a specific constant:
System.out.print("Enter day of week: ");
String input = scanner.next(); // get day from user
DayOfWeek dayInput = DayOfWeek.valueOf(input.toUpperCase());
In addition, the compiler adds a static values()
method to your enum. This values() method
returns an array of each enum constant. This allows you
to iterate through your constants, if you need to:
// for each day in the DayOfWeek enum
for (DayOfweek day : DayOfWeek.values()) {
System.out.printf("%d %s%n", day.ordinal(), day);
}
The code above will print the ordinal and constant name for
each enum constant in the DayOfWeek enum.
You can compare enum types using == just like you can with
primitive type variables:
if (firstCourse == Course.CHOC101) { ... }
Adding Values to Enumeration Constants
One thing you might wish to do with an enum is associate each
constant name with a value other than the ordinal.
For example,
maybe you want the days of the week to be associated with a String
value for the display name of each day, such as DayOfWeek.SUNDAY being
associated with "Sunday".
Another example: perhaps you want an enum for each Canadian coin
(or the coins from any country, for that matter):
The Coin enum constants would be more useful if, instead of having
ordinal values from 0 (PENNY) to 5 (TWONIE), we could associate the
monetary values for each coin: PENNY (.01), NICKLE (.05), TWONIE (2.0),
etc.
An advantage to enums in Java over other languages is that Java enums
can have other code in them, including methods and data
members!
This is how you can associate other values to enum constants: by adding
fields for those values. You will then need to add special constructors
to initialize those values. Let's do the DayOfWeek enum step by step.
1. Add The Values
First you need to add the values to each constant. You do this by
placing the value in parentheses after the constant name:
Note that until you finish all of the steps, you'll receive
a syntax error "Constructor DayOfWeek in enum DayOfWeek cannot be applied to
given types..." Don't worry about this for now. You'll be
fixing this (and the error will make sense then).
2. Add Data Member
The next thing is to add is a private data member for the day of
the week's display name. You will probably also want to add an
accessor method for the data member (you do not need a mutator
method) so you can retrieve its value.
Lastly, you need to add a special constructor to the enum:
the constructor is used to initailize the data member when a new enum
constant is used or created. Note there are some important
differences between an enum constructor and regular class
constructors:
Enum constructors must be private.
You can never use the constructor to constuct an instance
of the enum using the "new" operator.
No data validation is required in the constructor. The
constructor only executes whenever a new DayOfWeek variable is created
and assigned a value, or when a DayOfWeek constant is used. Therefore,
it's impossible for the String name parameter to receive a value that
isn't already defined with an enum constant.
Here's how it works:
DayOfWeek d = DayOfWeek.FRIDAY;
The above statement causes the DayOfWeek constructor to
execute. Since DayOfWeek.FRIDAY contains the value "Friday" in the
brackets, this is like calling new DayOfWeek("Friday").
The constructor accepts the String "Friday" into the name
parameter.
The constructor then takes String name
parameter value and assigns it to the
displayName data member variable.
Now, whenever the user wants to find out the display name
of a day of the week, they can call the getDisplayName()
method:
You can even rewrite the original example code
in a much easier and more efficient way now:
public static void main(String[] args) {
DayOfWeek d = DayOfWeek.FRIDAY;
System.out.println(d.getDisplayName());
}
Notice that we've replaced the entire switch statement with
one println() statement!
Exercise
Create the Coin enumeration and add a value for the coin value
(pennies = 1 cent, nickles = 5 cents, dimes = 10 cents, quarters
= 25 cents, loonies = 1 dollar, twonies = 2 dollars). Add
a data member variable, accessor method, and constructor.
Write a test program that creates some Coin variables and
displays the value of the coin.
public enum Coin {
// (coin value)
PENNY (.01),
NICKLE (.05),
DIME (.1),
QUARTER (.25),
LOONIE (1.0,),
TOONNIE (2.0);
private double coinValue; // the coin's value
/**
* Retrieve the value for this Coin.
*
* @return the coin value
*/
public double getCoinValue() {
return coinValue;
}
/*
* Used to set the coin value when a Coin constant
* is used. Note to students: don't javadoc private methods.
*/
private Coin(double value) {
// assign param to data member
coinValue = value;
}
}
public class TestCoin {
public static void main(String[] args) {
Coin c1 = Coin.DIME;
System.out.println(c1.getCoinValue());
}
}
Adding More than One Value
An enum constant can have more than one value (although each
constant is required to have the exact same number of values).
For example, perhaps for the DayOfWeek enum we want each constant to
have the display name, but also to have the numeric values from
1 to 7 so we don't have to use the ordinals from 0 to 6. Or
in the Coin enum, you want to have the coin values and the name
of the coin.
Adding more than one value is simple: just include the extra values
in the brackets for each constant (separate them with a comma) and
add the data member for each value. Finally, make sure the constructor
includes a parameter for each value and an assignment statement to
assign each parameter value to each corresponding data member.
1. Modify the Coin enum: add the display name of the coin
to each constant. Make sure you also add a data member, accessor method
for that data member, and that you modify the constructor accordingly.
Add a toString() override to the Coin enum that returns a Coin enum
value in the following format:
CoinName x.xx
Where CoinName is the actual display name of the coin (10 character
spaces) and x.xx is the coin's value (exactly 4 character spaces, including
2 decimal places padded with 0's where appropriate).
Write a test program that iterates through every coin in the enumeration
and prints the following output:
Canadian Coins:
Name Value
---------------
Penny 0.01
Nickle 0.05
Dime 0.10
Quarter 0.25
Loonie 1.00
Twonie 2.00
2. a. Modify the Suit enum: Assign
a display name for each suit and
a unicode character code for the suit's symbol. The
unicode values for each suit are in the table below.
Make sure you add the appropriate data members, accessor methods,
and a constructor.
Unicode Values for Suit Symbols
Clubs
2663
Diamonds
2666
Hearts
2665
Spades
2660
2. b. Modify the Rank enum: Assign a numeric value to each
card rank (Jacks are 11, Queens are 12, Kings are 13, and Aces are 1),
a name for each card (e.g. "Two", "Five", "Jack", "Ace"), and a
short name for each card (e.g "2", "5", "J", "A").
Add the appropriate data members, accessor methods, and a
constructor.
2. c. Create a regular class called Card with the following
members:
The mutator methods don't require validation since you can't
pass anything invalid into the parameters: just assign the
parameter to the data member.
The toString() method should return a String representation of the
card in the form: RankName of SuitName
For example, "Jack of Clubs", "Two of Diamonds".
The shortString() method should return a shorter String
representation of the card in the form: ShortRankSuitSymbol
For example: J♣ or
2♦
2. d. Create a regular class called DeckOfCards with the
following members:
The array of cards should have 52 elements for Card objects.
The DeckOfCards() constructor only needs to call the init()
method.
The init() method uses nested loops to load the cards array
with one of each card in a standard card deck (e.g. one of each
card rank with each suit).
The pickCard() method generates a random array index and
returns the Card object at that index in the cards array.
Make sure you can't pick the same random card more than once
(tip: keep track of which cards have been "used" -- this will require
several extra members)
A parallel boolean array (false = unused, true = used) although
if you're familiar with HashMap, you could use a map where the key
is the Card and the value is the Boolean.
resetUsed() - a private void method that resets the parallel array
to all false values (or just re-initialize the array, since false
is the default)
countUsedCards() - a public method that returns the number of
"used" cards (public because this could be useful outside the
class)
countAvailableCards() - a public method that returns the number
of "unused" cards (also useful outside the class)
The toString() method returns a String containing the
list of Card objects in the cards array (see sample program
and output below).
Sample main() method code using the DeckOfCards object:
DeckOfCards deck = new DeckOfCards();
System.out.println("My Deck of Cards:");
System.out.println(deck);
System.out.println("\nRandom Card:");
Card c = deck.pickCard();
System.out.println(c.shortString());
(Your editor might not display the symbols used in Card.shortString())
1. Updated Coin Enum
public enum Coin {
// (coin value, coin name)
PENNY (.01, "Penny"),
NICKLE (.05, "Nickle"),
DIME (.1, "Dime"),
QUARTER (.25, "Quarter"),
LOONIE (1.0, "Loonie"),
TOONNIE (2.0, "Twonie");
private double coinValue; // the coin's value
private String coinName; // the coin's name
public double getCoinValue() {
return coinValue;
}
public String getCoinName() {
return coinName;
}
/*
* Used to set the coin value and name when a Coin constant
* is used. Note to students: don't javadoc private methods.
*/
private Coin(double value, String name) {
// assign params to data members
coinValue = value;
coinName = name;
}
@Override
public String toString() {
// return a formatted string for this coin
return String.format("%-10s %4.2f", coinName, coinValue);
}
}
public class TestCoin {
public static void main(String[] args) {
System.out.println("Canadian Coins:\nName Value\n---------------");
for (Coin c : Coin.values()) {
System.out.println(c);
}
}
}
2. a) Updated Suit Enum
public enum Suit {
// (suit number, suit name, suit symbol)
CLUBS (1, "Clubs", '\u2663'),
DIAMONDS (2, "Diamonds", '\u2666'),
HEARTS (3, "Hearts", '\u2665'),
SPADES (4, "Spades", '\u2660');
private int suitNum; // suit number
private String suitName; // suit name
private char symbol; // suit symbol
public int getSuitNum() {
return suitNum;
}
public String getSuitName() {
return suitName;
}
public char getSymbol() { return symbol; }
// constructs an enum and sets all the fields
private Suit(int num, String name, char sym) {
// set the suit number, name, and symbol
suitNum = num;
suitName = name;
symbol = sym;
}
}
2. b) Updated Rank Enum
public enum Rank {
// (rank value, rank name, rank short name)
ACE (1, "Ace", "A"),
TWO (2, "Two", "2"),
THREE (3, "Three", "3"),
FOUR (4, "Four", "4"),
FIVE (5, "Five", "5"),
SIX (6, "Six", "6"),
SEVEN (7, "Seven", "7"),
EIGHT (8, "Eight", "8"),
NINE (9, "Nine", "9"),
TEN (10, "Ten", "10"),
JACK (11, "Jack", "J"),
QUEEN (12, "Queen", "Q"),
KING (13, "King", "K");
private int rankValue; // rank value
private String rankName; // long name
private String shortName; // short name
public int getRankValue() { return rankValue; }
public String getRankName() { return rankName; }
public String getShortName() { return shortName; }
// constructs a Rank enum value with the value,
// long name, and short name
private Rank(int value, String name, String sh) {
// assign value, long name, and short name
rankValue = value;
rankName = name;
shortName = sh;
}
}
2. c) Card Class:
public class Card {
private Rank rank; // the card's rank
private Suit suit; // the card's suit
public Card() {
}
public Card(Rank r, Suit s) {
// assign the rank and suit
rank = r;
suit = s;
/* NOTE: no mutator calls required in this
case because rank and suit can't be invalid. */
}
public void setRank(Rank r) {
// place the programmer-specified rank into the rank member
rank = r;
}
public Rank getRank() {
return rank;
}
public void setSuit(Suit s) {
// place the programmer-specified suit into the suit member
suit = s;
}
public Suit getSuit() {
return suit;
}
@Override
public String toString() {
// return a formatted string for this card
return rank.getRankName() + " of " + suit.getSuitName();
}
public String shortString() {
// return a formatted short string for this card
return rank.getShortName() + suit.getSymbol();
}
}
2. d) DeckOfCards Class:
public class DeckOfCards {
// note: you could use a HashMap<Card><Boolean> instead of 2 arrays
// the array of Card objects that make up a deck
private final Card[] cards = new Card[52];
// parallel array of flags used to determine which cards were
// "used" in dealing cards from the deck (true is used, false is not used)
private boolean[] used = new boolean[52];
public DeckOfCards() {
// load the array of card objects
init();
}
private void init() {
// reset the used flags
resetUsed(); // all cards are unused to start with
// card array index - start at beginning
int index = 0;
// for each suit in the Suit enum
for (Suit s : Suit.values()) {
// for each rank in the rank enum
for (Rank r : Rank.values()) {
// assign a new Card object to the cards array
cards[index++] = new Card(r, s);
}
}
}
private void resetUsed() {
// reset the used array (default initial value is always false/unused)
used = new boolean[52];
}
public int countUsedCards() {
// start off with 0 cards available
int count = 0;
// loop through the used flag array
for (int i = 0; i < cards.length; i++) {
// increment counter for each card not used
if (used[i]) {
count++;
}
}
return count;
}
public int countAvailableCards() {
// number of available cards = total cards minus used cards
return cards.length - countUsedCards();
}
public Card pickCard() {
// get the number of available Cards
int numCards = countAvailableCards();
// make sure we have enough cards from which to pick
if (numCards <= 0) {
throw new IllegalArgumentException("Not enough cards.");
}
// generate a random index value
int r = (int) (Math.random() * 52);
// if this card is already used, find a new one
boolean up = Math.random() < .5; // pick a direction
int direction = (up) ? 1 : -1; // set increment or decrement
// while we find only used cards
while (used[r]) {
// if direction is up, search up the array
if (up) {
// when we reach the end, go back to 0
if (r == cards.length - 1)
r = -1;
} else { // search down the array
// when we reach the beginning, go to end
if (r == 0)
r = cards.length;
}
// go to next/previous card
r += direction;
}
// mark this card as used
used[r] = true;
// return the Card object at the found index
return cards[r];
}
@Override
public String toString() {
// initialize output string
String output = "";
// for each card in the array of Card objects
for (Card c : cards) {
// add this card object's string to the output string
output += c.toString() + "\n";
}
// return the output string
return output;
}
}
Advanced Technique: Reverse Look-up
You'll often want to do a "reverse look-up" in your enum:
For example, you might have a price value and want to know
the name of the FabricType that belongs to that price. Or you
might have the name of a Coin and want the Coin enum constant
that name belongs to.
For this demonstration, I'll be using the Products enum
which contains a list of Product constants. Each constant
contains an inventory ID number as an integer and a Product
name as a String.
public enum Products {
LITTER (15, "Cat Litter"),
MOUSE_TOY (112, "Furry Mouse Toy"),
SMALL_TREE (2, "Small Cat Tree"),
LARGE_TREE (3, "Large Cat Tree"),
SMALL_KIBBLE (37, "Small Bag Kibble"),
LARGE_KIBBLE (39, "Large Bag Kibble");
private int id;
private String displayName;
public int getId() {
return id;
}
public String getDisplayName() {
return displayName;
}
private Products(int id, String name) {
this.id = id;
displayName = name;
}
}
We're going to add a reverse lookup that allows someone to
look for a specific Products enum constant by the ID value.
Getting this to work is a bit more complicated and requires the use
of the Map and HashMap classes.
First, you need to create a private HashMap object inside the enum class.
This HashMap will contain a set of key-value pairs where the key
is the value you'll want to look up (for example, an integer id
belonging to a particular Product), and the value will
be the actual enum constant.
Next, you create public methods that allow you to access specific
elements of the HashMap by key. For example, you can pass the method
the integer 37 and it will return Products.SMALL_KIBBLE
from the HashMap.
1. Create the HashMap Object
A HashMap is just like a list of key-value pairs: each element requires
a unique key and a value. When you create an instance of HashMap,
you have to specify a concrete type for the key and the value.
In the Products example, our key is the id number (int) and the
value is a Products enum constant. Note that collections can only
contain objects, so all primitives must be boxed into wrapper
class objects (e.g. int -> Integer).
The HashMap data member must be a class variable:
it should be defined as static because the methods we're going
to write to perform a lookup also have to be static, and you
can't access a non-static variable from a static method.
2. Create Method to Init HashMap
Next, we create a private helper method that initializes the
HashMap by creating elements that contain the id number as
the key and the corresponding Products constant as the value:
private static void initIdLookup() {
// create the HashMap object with Integer keys and products values
lookupById = new HashMap<Integer, Products>();
// for each Products constant in the enum:
for (Products p : values()) {
// put a new element into the HashMap, using
// the id number as the key and the constant as the value
lookupById.put(p.id, p);
}
}
This method must be static because we'll be calling it from the
actual lookup method, and the lookup method must be static. You
can't call a non-static method from a static method.
The first line that creates the HashMap doesn't actually need
the <Integer, Products> (in fact, your editor might give you a
warning to that effect). I left it here for clarity, but in
your own code you can leave that out and just put empty
<>. This is because the concrete types were already
defined when you first declared the HashMap data member.
You'll learn more about this in a later lesson.
The For-Loop iterates through every constant in the
Products enum (using the values() method, which you'll recall
returns an array of all the constants). For each enum
constant, we put() a new element into the HashMap, using
the id number field value as the key and the actual
Products constant as the value.
3. Create Public Look-Up Method
The last step is to create the public method that allows
a programmer to look up a Products constant by its id number.
The method should accept an integer for the id number
and return the matching Products enum constant:
public static Products getProduct(int num) {
// check first to make sure we haven't already
// initialized the HashMap
if (lookupById == null) {
// load the HashMap
initIdLookup();
}
// get the Products constant for the specified day number
Product product = lookupById.get(num);
// if there was nothing returned, the ID number is invalid
if (product == null) {
throw new IllegalArgumentException("Invalid product ID number.");
}
// return the Product constant for the id number specified
return product;
}
The first thing we do in this method is to make sure that
the HashMap object for the lookup hasn't already been
initialized: if the HashMap is null, then we haven't filled
it with key-value pairs yet, so we call the init method
we wrote in step 2.
You could probably call this method in the constructor instead
without an if-statement, and I considered doing that. However,
you don't need the lookup every time you use or create a Products
constant, so it's actually more efficient to only create it
when we know we'll need it. We know we'll need it if the programmer
calls the lookup method, so I put it here instead to make
the class more efficient.
After the HashMap is initialized, if needed, we use the HashMap
get() method to get a specific object by index. In this case,
the id number we passed into the method is the index: this is the
id number we have, and we want to know what Products constant matches
that id number. The get() method will return the value that
corresponds to that key. So for example, if we pass in the integer
37, then lookupById.get(37) will return
Products.SMALL_KIBBLE. We store
this constant in the Products variable product.
If the id number we requested is not a valid index in the
lookupById HashMap, then the get()
method will return a null
object. If that's the case, then we throw an exception with
the message that the ID number passed into the method was not
a valid id number.
If the Products object returned from the get()
method isn't a null object, then it must have been a valid id
number, so we return that Products constant from the method.
The getProduct() method must be static. This is because we need to
be able to invoke the method without having an actual
Products enum constant in existence. In other words,
we should be able to say:
Product prod = Products.getProduct(37);
We wouldn't invoke this method on a specific Products enum constant
because that wouldn't make any sense. We wouldn't say:
Remember that we're using getProduct() because we have an id number
and don't know which constant that matches - we're trying to find
that out. So we need to be able to use getProduct() without an instance
or actual Products constant.
The entire products class now looks like this:
public enum Products {
LITTER (15, "Cat Litter"),
MOUSE_TOY (112, "Furry Mouse Toy"),
SMALL_TREE (2, "Small Cat Tree"),
LARGE_TREE (3, "Large Cat Tree"),
SMALL_KIBBLE (37, "Small Bag Kibble"),
LARGE_KIBBLE (39, "Large Bag Kibble");
private int id;
private String displayName;
private static HashMap<Integer, Products> lookupById = null;
public int getId() {
return id;
}
public String getDisplayName() {
return displayName;
}
public static Products getProduct(int num) {
if (lookupById == null) {
initIdLookup();
}
Product product = lookupById.get(num);
if (product == null) {
throw new IllegalArgumentException("Invalid product ID number.");
}
return product;
}
private Products(int id, String name) {
this.id = id;
displayName = name;
}
private static void initIdLookup() {
lookupById = new HashMap<Integer, Products>();
for (Products p : values()) {
lookupById.put(p.id, p);
}
}
}
You can now use the Products.getProduct()
method to retrieve a Products constant
by it's id number. You could even use this to find out the display
name of a specific Products constant:
int id = 5;
Products p = Products.getProduct(id);
System.out.println(p);
System.out.println(p.getDisplayName());
What if you had a String for a product name and wanted to know which
product ID that was? You could also add similar code to do a
lookup by the product name:
private static HashMap<String, Products> lookupByName = null;
...
private static void initNameLookup() {
lookupByName = new HashMap<String, Products>();
for (Products p : values()) {
lookupByName.put(p.displayName, p);
}
}
public static Products getProduct(String name) {
if (lookupByName == null) {
initNameLookup();
}
Product p = lookupByName.get(name);
if (p == null) {
throw new IllegalArgumentException("Invalid product name.");
}
return p;
}
Note that this second getProduct() method is an overload of the
previous getProduct() method. There are two versions of
getProduct() - one that accepts an int
for the id number and one that accepts a String for the product name.
You can test this out the same way you tested the id lookup:
1. Modify the Coin enumeration: add the ability for a
programmer to look up a Coin enum by it's name. For example,
looking up "Quarter" should produce Coin.QUARTER.
Write a main() method that accepts a coin name from the user
and displays the denomination for that coin. Display an error
if the user enters a coin name that doesn't exist.
2. a. Modify the Suit enum: add the ability for a programmer
to look up a Suit constant the suit number.
2. b. Modify the Rank enum: add the ability for a programmer
to look up a specific Rank constant by its value or its name.
2. c. Write a main() method that generates a random suit
number and a random rank value. Then display the random suit and rank
info in the format below. Generate 5 cards this way.
Jack of Clubs
Two of Diamonds
Four of Diamonds
Jack of Hearts
Queen of Spades
1. Updated Coin Enum:
public enum Coin {
// (coin value, coin name)
PENNY (.01, "Penny"),
NICKLE (.05, "Nickle"),
DIME (.1, "Dime"),
QUARTER (.25, "Quarter"),
LOONIE (1.0, "Loonie"),
TOONNIE (2.0, "Twonie");
private double coinValue; // the coin's value
private String coinName; // the coin's name
// HashMap object for look-up (advanced technique)
private static HashMap<String, Coin> lookupByName = null;
public double getCoinValue() {
return coinValue;
}
public String getCoinName() {
return coinName;
}
private Coin(double value, String name) {
// assign params to data members
coinValue = value;
coinName = name;
}
public static Coin getCoin(String name) {
// check first to make sure we haven't already
// initialized the HashMap
if (lookupByName == null) {
// load the hash map
initNameLookup();
}
// get the Coin constant for the specified coin name
Coin c = lookupByName.get(name.toLowerCase());
// if there was nothing returned, the coin name is invalid
if (c == null) {
throw new IllegalArgumentException("Invalid coin name.");
}
// return the Coin constant for the name specified
return c;
}
@Override
public String toString() {
// return a formatted string for this coin
return String.format("%-10s %4.2f", coinName, coinValue);
}
/*
* Loads the hash map with key-value pairs where the key is the
* day name and the value is the matching Day constant. This
* hash map is used to look up a Day constant by the name of the
* day of the week.
*/
private static void initNameLookup() {
// create the HashMap object with String keys and Day values
// note that you don't need the concrete types here: I only
// included them because we haven't learned this yet
lookupByName = new HashMap();
// for each Day constant in the enum:
for (Coin c : values()) {
// put a new element into the HashMap, using
// the coin name as the key and the constant as the value
lookupByName.put(c.coinName.toLowerCase(), c);
}
}
}
public class TestCoin {
public static void main(String[] args) {
// scanner for user input
Scanner in = new Scanner(System.in);
// get coin name from user
System.out.print("Enter coin name: ");
String coinName = in.nextLine();
try {
// get the coin constant for this coin name
Coin c = Coin.getCoin(coinName);
// display value of coin
System.out.printf("The value of a %s is %d cents.%n",
c.getCoinName(), (int)(c.getCoinValue() * 100));
} catch (IllegalArgumentException ex) {
// invalid coin name error message
System.out.println(ex.getMessage());
}
}
}
2. a) Updated Suit Enum
public enum Suit {
// (suit number, suit name, suit symbol)
CLUBS (1, "Clubs", '\u2663'),
DIAMONDS (2, "Diamonds", '\u2666'),
HEARTS (3, "Hearts", '\u2665'),
SPADES (4, "Spades", '\u2660');
private int suitNum; // suit number
private String suitName; // suit name
private char symbol; // suit symbol
// HashMap objects for look-ups
private static HashMap<Integer, Suit> lookupByNumber = null;
public int getSuitNum() { return suitNum; }
public String getSuitName() { return suitName; }
public char getSymbol() { return symbol; }
// constructs an enum and sets all the fields
private Suit(int num, String name, char sym) {
// set the suit number, name, and symbol
suitNum = num;
suitName = name;
symbol = sym;
}
public static Suit getSuit(int num) {
// check first to make sure we haven't already
// initialized the HashMap
if (lookupByNumber == null) {
// load the HashMap
initNumberLookup();
}
// get the Suit constant for the specified suit number
Suit s = lookupByNumber.get(num);
// if there was nothing returned, the suit number is invalid
if (s == null) {
throw new IllegalArgumentException("Invalid suit number.");
}
// return the Suit constant for the suit number specified
return s;
}
private static void initNumberLookup() {
// create the HashMap object with Integer keys and Suit values
lookupByNumber = new HashMap<Integer, Suit>();
// for each Suit constant in the enum:
for (Suit s : values()) {
// put a new element into the HashMap, using
// the suit number as the key and the constant as the value
lookupByNumber.put(s.suitNum, s);
}
}
}
2. b) Updated Rank Enum:
public enum Rank {
// (rank value, rank name, rank short name)
ACE (1, "Ace", "A"),
TWO (2, "Two", "2"),
THREE (3, "Three", "3"),
FOUR (4, "Four", "4"),
FIVE (5, "Five", "5"),
SIX (6, "Six", "6"),
SEVEN (7, "Seven", "7"),
EIGHT (8, "Eight", "8"),
NINE (9, "Nine", "9"),
TEN (10, "Ten", "10"),
JACK (11, "Jack", "J"),
QUEEN (12, "Queen", "Q"),
KING (13, "King", "K");
private int rankValue; // rank value
private String rankName; // long name
private String shortName; // short name
// HashMap objects for look-ups
private static HashMap<Integer, Rank> lookupByNumber = null;
private static HashMap<String, Rank> lookupByName = null;
public int getRankValue() { return rankValue; }
public String getRankName() { return rankName; }
public String getShortName() { return shortName; }
// constructs a Rank enum value with the value,
// long name, and short name
private Rank(int value, String name, String sh) {
// assign value, long name, and short name
rankValue = value;
rankName = name;
shortName = sh;
}
public static Rank getRank(int num) {
// check first to make sure we haven't already
// initialized the HashMap
if (lookupByNumber == null) {
// load the HashMap
initNumberLookup();
}
// get the Rank constant for the specified value
Rank r = lookupByNumber.get(num);
// if there was nothing returned, the rank value is invalid
if (r == null) {
throw new IllegalArgumentException("Invalid suit number.");
}
// return the Rank constant for the value specified
return r;
}
public static Rank getRank(String name) {
// check first to make sure we haven't already
// initialized the HashMap
if (lookupByName == null) {
// load the HashMap
initNameLookup();
}
// get the Rank constant for the specified rank name
Rank r = lookupByName.get(name);
// if there was nothing returned, the rank name is invalid
if (r == null) {
throw new IllegalArgumentException("Invalid rank name.");
}
// return the Rank constant for the name specified
return r;
}
/*
* Loads the hash map with key-value pairs where the key is the
* Rank value and the value is the matching Rank constant. This
* hash map is used to look up a Rank constant by it's value.
*/
private static void initNumberLookup() {
// create the HashMap object with Integer keys and Rank values
lookupByNumber = new HashMap();
// for each Rank constant in the enum:
for (Rank r : values()) {
// put a new element into the HashMap, using
// the rank value as the key and the constant as the value
lookupByNumber.put(r.rankValue, r);
}
}
/*
* Loads the hash map with key-value pairs where the key is the
* Rank name and the value is the matching Rank constant. This
* hash map is used to look up a Rank constant by it's name.
*/
private static void initNameLookup() {
// create the HashMap object with String keys and Rank values
lookupByName = new HashMap();
// for each Rank constant in the enum:
for (Rank r : values()) {
// put a new element into the HashMap, using
// the rank name as the key and the constant as the value
lookupByName.put(r.rankName, r);
}
}
}
2. c) Main that picks 5 random cards:
I added the following method to the DeckOfCards class:
public Card[] dealCards(int num) {
// make sure number of cards to deal is valid
if (num >= cards.length || num <= 0) {
throw new IllegalArgumentException("Invalid number of cards.");
}
// make sure there's enough cards
if (num > countAvailableCards()) {
throw new IllegalArgumentException("Not enough cards.");
}
// create an array to hold the cards we want to deal
Card[] hand = new Card[num];
// pick random cards
for (int i = 0; i < num; i++) {
hand[i] = pickCard();
}
// return the hand of dealt cards
return hand;
}
Main class code in the main():
DeckOfCards deck = new DeckOfCards();
Card[] handOfCards = deck.dealCards(5);
for (Card c : handOfCards) {
System.out.println(c);
}
JavaDocs for Enums
When writing your Javadocs for your enums, make sure you document
all public methods. Also, since your constants are public, you should
document those, too. I typically document each enum constant with
it's associated values. For example:
/**
* A set of constants for the various fabric types offered for custom
* cat trees.
*/
public enum FabricType {
/**
* 5.95, Premium Fabrics
*/
PREMIUM (5.95, "Premium Fabrics"),
/**
* 4.50, Basic Fabrics
*/
BASIC (4.50, "Basic Fabrics");
private double price; // price of this fabric
private String displayName; // name of this fabric
/**
* Retrieves the price of this fabric.
*
* @return this fabric type's price
*/
public double getPrice() { return price; }
/**
* Retrieves the name of this fabric.
*
* @return this fabric type's name
*/
public String getDisplayName() { return displayName; }
private Fabrictype(double price, String name) {
this.price = price;
displayName = name;
}
}