Overview of This Lesson

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:

  1. 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.
  2. 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".
  3. 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
}

2. Rank enum:

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

(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:

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):

public enum Coin {

    PENNY,
    NICKLE,
    DIME,
    QUARTER,
    LOONIE,
    TWONIE;
}

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:

public enum DayOfWeek {

    SUNDAY ("Sunday"),
    MONDAY ("Monday"),
    TUESDAY ("Tuesday"),
    WEDNESDAY ("Wednesday"),
    THURSDAY ("Thursday"),
    FRIDAY ("Friday"),
    SATURDAY ("Saturday");
}

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.

public enum DayOfWeek {

    SUNDAY ("Sunday"),
    MONDAY ("Monday"),
    TUESDAY ("Tuesday"),
    WEDNESDAY ("Wednesday"),
    THURSDAY ("Thursday"),
    FRIDAY ("Friday"),
    SATURDAY ("Saturday");

    private String displayName;

    public String getDisplayName() { 
        return displayName; 
    }


}

3. Add a Constructor

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:

public enum DayOfWeek {

    SUNDAY ("Sunday"),
    MONDAY ("Monday"),
    TUESDAY ("Tuesday"),
    WEDNESDAY ("Wednesday"),
    THURSDAY ("Thursday"),
    FRIDAY ("Friday"),
    SATURDAY ("Saturday");

    private String displayName;

    public String getDisplayName() { 
        return displayName; 
    }

    private DayOfWeek(String name) {
        displayName = name;
    }

}

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:

System.out.println(DayOfWeek.MONDAY.getDisplayName());

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.

Example:

public enum FabricType {
    PREMIUM (5.95, "Premium Fabrics"),
    BASIC (4.50, "Basic Fabrics");
    
    private double price;
    private String displayName;
    
    public double getPrice() { 
        return price; 
    }
    
    public String getDisplayName() { 
        return displayName; 
    }
    
    private Fabrictype(double price, String name) {
        this.price = price;
        displayName = name;
    }
}

Note that the order of values for each constant match the order of parameters in the constructor. This means that the following would not compile:

public enum FabricType {
    PREMIUM ("Premium Fabrics", 5.95),
    BASIC ("Basic Fabrics", 4.50);

    ...

    private FabricType(double price, String name) { ... }

}

For the same reason, the following would also be invalid:

public enum Fabrictype {
    PREMIUM (5.99, "Premium Fabrics"),
    BASIC (4.50, "Basic Fabrics");
    
    ...

    private FabricType(String name, double price) { ... }

}

Exercises

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:

Class: Card
Data Members:
- rank : Rank
- suit : Suit
Methods:
+ Card()
+ Card(rank:Rank, suit:Suit)
+ setRank(rank:Rank) : void
+ getRank() : Rank
+ setSuit(suit:Suit) : void
+ getSuit() : Suit
+ toString() : String
+shortString() : String

2. d. Create a regular class called DeckOfCards with the following members:

Class: DeckOfCards
Data Members:
- cards: Card[]
Methods:
+ DeckOfCards()
- init() : void
+ pickCard() : Card
+ toString() : String

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).

private static HashMap<Integer, Products> lookupById = null;

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:

Products prod = Products.SMALL_KIBBLE.getProduct(37);

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:

String prodName = "Furry Mouse Toy";
Product prod = Products.getProduct(prodName);
System.out.println(prod);
System.out.println(prod.getId());

Exercises

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;
    }
}