Monday, September 29, 2014

Java Enum

Suppose you were developing a card game in Java. To make things simpler let's just focus on card suits.

A card can be one of four suits: Hearts, Diamonds, Spades, or Clubs.

So you need a way to record what suit a Card is. Suppose you do that using the following constants:
public class Card {
   public static final int HEARTS = 1;
   public static final int DIAMONDS = 2;
   public static final int SPADES = 3;
   public static final int CLUBS = 4;

   // ...
You also have the following get and set methods to retrieve and store what suit a card is:
public class Card {
   // ...

   private int suit = HEARTS;

   public int getSuit() {
      return suit;
   }

   public void setSuit(int suit) {
      this.suit = suit;
   }
Looks good, right? But what's to stop somebody from setting the card to a non-existent suit, like so:
card.setSuit(12345);
Your first solution to this problem might be to check that the argument passed in is valid.
public void setSuit(int suit) {
   if((suit < HEARTS) || (suit > CLUBS)) {
      throw new IllegalArgumentException(suit + " is not a suit.");
   }

   this.suit = suit;
}
But wouldn't it be better to catch the problem before it got to the client. Also, what's to stop a programmer (whether intentional or not) from returning a non-existent suit.
public int getSuit() {
   return 12345;
}
This is where enums come in. Enums are great when you have a fixed set of named constants, like card suits. Enums were first introduced in Java 5 and are defined using the enum keyword.
public enum Suit {
   HEARTS, DIAMONDS, SPADES, CLUBS;
}
Enums can be defined either inside a class or in their own separate Java file (ie. Suit.java).

The semicolon ; after the last element is optional when it is the end of the enum definition.
public enum Suit {
   HEARTS, DIAMONDS, SPADES, CLUBS
}
Otherwise, it's required.
public enum Suit {
   HEARTS, DIAMONDS, SPADES, CLUBS;

   private int fooBar;
}
As you can see from the previous example, enums can have fields. You don't have to initialize the field when you declare it, though. The field will be assigned an appropriate default value if you don't.
public enum Suit {
      HEARTS, DIAMONDS, SPADES, CLUBS;

      public int i;
      public boolean bool;
      public String str;
   }

   public static void main(String[] args) {
      System.out.println("The default value for an int is " + HEARTS.i);
      System.out.println("The default value for a boolean is " + HEARTS.bool);
      System.out.println("The default value for an Object is " + HEARTS.str);
   }
}
Output
The default value for an int is 0
The default value for a boolean is false
The default value for an Object is null
Each element of an enum (ie. HEARTS, DIAMONDS, SPADES, CLUBS) is a Singleton. That is, only one instance of the element ever gets created. (In his book Effective Java, second edition, Joshua Bloch recommends using enums as an efficient way to implement the singleton pattern.)
public static void main(String[] args) {
   Suit h1 = HEARTS;
   Suit h2 = HEARTS;

   System.out.println("same object reference? " + h1 == h2);
   System.out.println("objects have same state? " + h1.equals(h2));
}
Output
same object reference? true
objects have same state? true
Since each enum element is a Singleton that means each enum element gets its own set of fields. That does not, however, mean that each field is implicitly declared as being static. (It's static to the element but not to the enum type as a whole).
public enum Suit {
   HEARTS, DIAMONDS, SPADES, CLUBS;

   public static String strStatic;
   public String str;

   public static void main(String[] args) {
      HEARTS.strStatic = "foo";
      HEARTS.str = "bar";

      System.out.println("strStatic = " + CLUBS.strStatic);
      System.out.println("str = " + CLUBS.str);
   }
}
Output
strStatic = foo
str = null
If you haven't noticed already, when you assign a value to an enum variable the name of the enum element is case sensitive:
Suit good = Suit.HEARTS;
Suit bad = Suit.Hearts; // Compiler Error: Cannot find symbol 'Hearts'
Also, enums can have methods. They can even have abstract methods.
public enum Suit {

   // Note: The following example is a modified version Javin Paul's example

   HEARTS() {

      @Override
      public String getColor() {
         return "red";
      }
   },
   DIAMONDS() {

      @Override
      public String getColor() {
         return "red";
      }
   },
   SPADES() {

      @Override
      public String getColor() {
         return "black";
      }
   },
   CLUBS() {

      @Override
      public String getColor() {
         return "black";
      }
   };

   public abstract String getColor();


   public static void main(String[] args) {
      System.out.println("Hearts are " + HEARTS.getColor());
      System.out.println("Clubs are " + CLUBS.getColor());
   }
}
Output
Hearts are red
Clubs are black
When an enum contains fields and/or methods, the definition of the fields and methods must always come after the list of elements of the enum.
public enum Suit {
   private String str;
   
   HEARTS, DIAMONDS, SPADES, CLUBS; // Compiler Error
}
Here's the correct way to do that:
public enum Suit {   
   HEARTS, DIAMONDS, SPADES, CLUBS;

   private String str;
}
Enums can have constructors. You can either leave the access modifier for the constructor as is, which is the default package access.
public enum Suit {   
   HEARTS, DIAMONDS, SPADES, CLUBS;

   Suit() {
      System.out.println(this.name() + "'s constructor");
   }

   public static void main(String[] args) {
   }
}
Output
HEARTS's constructor
DIAMONDS's constructor
SPADES's constructor
CLUBS's constructor
Or you can declare it as private:
public enum Suit {   
   HEARTS, DIAMONDS, SPADES, CLUBS;

   private Suit() {
      System.out.println(this.name() + "'s constructor");
   }

   public static void main(String[] args) {
   }
}
Output
HEARTS's constructor
DIAMONDS's constructor
SPADES's constructor
CLUBS's constructor
You are not, however, allowed to declare the constructor as having either protected or public access.
public enum Suit {   
   HEARTS, DIAMONDS, SPADES, CLUBS;

   protected Suit() { // Compiler Error: Modifier 'protected' not allowed here
      System.out.println(this.name() + "'s constructor");
   }

   public static void main(String[] args) {
   }
}
To be honest, though, I don't really know what the advantages or disadvantages of making the enum's constructor private are as you can't instantiate an instance of an enum using the new keyword:
public enum Suit {
   HEARTS, DIAMONDS, SPADES, CLUBS;

   Suit() {
   }

   public static void main(String[] args) {
      Suit suit = new Suit(); // Compiler Error: Enum types cannot be instantiated
   }
}
And it's not like you're ever going to override the constructor since you can't declare an enum that extends from another enum:
public enum Parent {
  MOM, DAD;
}

public enum Child extends Parent { // Compiler Error: Cannot inherit from Parent
  DAUGHTER, SON;
}
In fact, you can't even use the extends keyword with enums.
public enum Suit extends Klass { // Compiler Error: No extends clause allowed for enum
   HEARTS, DIAMONDS, SPADES, CLUBS;
}
Check out the post I did on access modifiers in Java.

Even though our Suit enum doesn't explicitly extend from any class, all enums implicitly extend from the java.lang.Enum class.
System.out.println(Suit.HEARTS instanceof Enum); // true
All enums implicitly implement java.io.Serializable and java.lang.Comparable.
System.out.println(Suit.HEARTS instanceof java.io.Serializable); // true
System.out.println(Suit.SPADES instanceof Comparable); // true
Before I talk about the other features of enums let's go back to our original problem. Recall that there was no way to guarantee that a Card returned a valid suit value.
public int getSuit() {
   return 12345;
}
With enums, such problems crop up early on during the compilation stage, rather than during runtime. Compiler errors are better than runtime errors because the cause of a runtime error may be difficult to track down.
public Suit getSutie() {
   return 12345; // Compiler Error: Incompatible types
}
You can use enums in switch statements:
public enum Suit {
   HEARTS, DIAMONDS, SPADES, CLUBS;

   public static void main(String[] args) {
      Suit suit = Suit.HEARTS;

      switch(suit) {
         case HEARTS:
            System.out.println("HEARTS");
            break;
      }
   }
}
Notice that you don't prefix the element name in the case statement with the name of the enum. That is, you don't do this:
public enum Suit {
   HEARTS, DIAMONDS, SPADES, CLUBS;

   public static void main(String[] args) {
      Suit suit = Suit.HEARTS;

      switch(suit) {
         case Suit.HEARTS: // Compiler Error: An enum switch case label must be the unqualified name of an enumeration constant
            System.out.println("HEARTS");
            break;
      }
   }
}
All enums have a name() method which just returns the name of the enum element.
public enum Suit {
   HEARTS, DIAMONDS, SPADES, CLUBS;

   public static void main(String[] args) {
      System.out.println(Suit.HEARTS.name());
   }
}
Output
HEARTS
Enums also have an ordinal() method which returns an integer value indicating the position of the element in the enum.
public enum Suit {
   HEARTS, DIAMONDS, SPADES, CLUBS;

   public static void main(String[] args) {
      System.out.println("Heart's ordinal() is " + HEARTS.ordinal());
      System.out.println("Diamond's ordinal() is " + DIAMONDS.ordinal());
      System.out.println("Spade's ordinal() is " + SPADES.ordinal());
      System.out.println("Club's ordinal() is " + CLUBS.ordinal());
   }
}
Output
Heart's ordinal() is 0
Diamond's ordinal() is 1
Spade's ordinal() is 2
Club's ordinal() is 3
You can iterate over all the elements of an enum using the implicit values() method.
public enum Suit {
   HEARTS, DIAMONDS, SPADES, CLUBS;

   public static void main(String[] args) {
      for(Suit suit : Suit.values( )) {
         System.out.println(suit.name() + "' ordinal is " + suit.ordinal());
      }
   }
}
Output
HEARTS' ordinal is 0
DIAMONDS' ordinal is 1
SPADES' ordinal is 2
CLUBS' ordinal is 3
References
The Java Tutorials : Enum Types

Jakob Jenkov's article titled "Java Enums"

Javin Paul blog post titled "Java Enum Tutorial: 10 Examples of Enum in Java", which was posted on Saturday, August 13, 2011.

A blog post titled "Java enum examples" that was posted at Java How To's... on April 13, 2008.

No comments:

Post a Comment