Thursday, July 8, 2010

The Sleek EnumMap and EnumSet

After having spent several years developing primarily in C++, I missed having an enum in Java until it was finally introduced with J2SE 5.  The long wait was worth it because the Java enum is much more useful and powerful than its C++ counterpart.  Although the Java enum can be used with any Java collection, its full power is best leveraged when used with the EnumMap and EnumSet.

Why would I use an EnumMap rather than a HashMap?  The primary reasons boil down to some inherent advantages of Java's enum as stated in the Javadoc documentation for EnumMap: "Enum maps are represented internally as arrays. This representation is extremely compact and efficient." Later in the same Javadoc documentation, there is an "Implementation Note" that states: "All basic operations execute in constant time. They are likely (though not guaranteed) to be faster than their HashMap counterparts."

The Javadoc documentation states similar advantages for the EnumSet over the HashSet:
Enum sets are represented internally as bit vectors. This representation is extremely compact and efficient. The space and time performance of this class should be good enough to allow its use as a high-quality, typesafe alternative to traditional int-based 'bit flags.'  ... Implementation note: All basic operations execute in constant time. They are likely (though not guaranteed) to be much faster than their HashSet counterparts. Even bulk operations execute in constant time if their argument is also an enum set.
Compact and efficient, these enum-powered collections are also easy to use as demonstrated in the following code listings.  First, an example enum is provided in ColorEnum.  Then, the class EnumCollections.java provides some simple demonstrations of EnumMap and EnumSet in action.  In particular, the code listing demonstrates different ways to instantiate instances of these (using constructors for EnumMap and static initialization factories for EnumSet).

ColorEnum.java

package dustin.examples;

import java.awt.Color;

/**
 * Enum representing colors.
 *
 * @author Dustin
 */
public enum ColorEnum
{
   BLACK("#000000", Color.BLACK, new RedGreenBlue(0, 0, 0)),
   BLUE("#0000FF", Color.BLUE, new RedGreenBlue(0, 0, 255)),
   CYAN("#00FFFF", Color.CYAN, new RedGreenBlue(0, 255, 255)),
   GRAY("#808080", Color.GRAY, new RedGreenBlue(128, 128, 128)),
   GREEN("#00FF00", Color.GREEN, new RedGreenBlue(0, 159, 107)),
   MAGENTA("#FF00FF", Color.MAGENTA, new RedGreenBlue(255, 0, 255)),
   ORANGE("#FF8040", Color.ORANGE, new RedGreenBlue(255, 127, 0)),
   PINK("#FFC0CB", Color.PINK, new RedGreenBlue(255, 192, 203)),
   RED("#FF0000", Color.RED, new RedGreenBlue(255, 0, 0)),
   WHITE("#FFFFFF", Color.WHITE, new RedGreenBlue(255, 255, 255)),
   YELLOW("#FFFF00", Color.YELLOW, new RedGreenBlue(255, 205, 0));

   /**
    * Parameterized constructor.
    *
    * @param newHtmlHex HTML hex code for this color.
    * @param newJavaColor Color from java.awt package.
    */
   ColorEnum(
      final String newHtmlHex,
      final Color newJavaColor,
      final RedGreenBlue newRgb)
   {
      this.htmlHexCode = newHtmlHex;
      this.javaColorCode = newJavaColor;
      this.rgb = newRgb;
   }

   /** HTML hexadecimal code for this color. */
   private final String htmlHexCode;

   /** java.awt.Color code. */
   private final Color javaColorCode;

   /** RGB. */
   private final RedGreenBlue rgb;

   /**
    * Provide the java.awt.Color code for this color.
    *
    * @return The java.awt.Color code.
    */
   public Color getJavaColorCode()
   {
      return this.javaColorCode;
   }

   /**
    * Provide the HTML hexadecimal code for this color.
    *
    * @return HTML hexadecimal code for this color.
    */
   public String getHtmlHexadecimalCode()
   {
      return this.htmlHexCode;
   }

   /**
    * Provide RGB representation of this color.
    *
    * @return RGB representation of this color.
    */
   public String getRgbRepresentation()
   {
      return this.rgb.getRgbString();
   }

   /**
    * Represents RGB.
    */
   public static class RedGreenBlue
   {
      /** Red portion of RGB. */
      private final int redValue;

      /** Green portion of RGB. */
      private final int greenValue;

      /** Blue portion of RGB. */
      private final int blueValue;

      public RedGreenBlue(
         final int newRedValue, final int newGreenValue, final int newBlueValue)
      {
         this.redValue = newRedValue;
         this.greenValue = newGreenValue;
         this.blueValue = newBlueValue;
      }

      /**
       * Provide the red value of my RGB.
       *
       * @return Red portion of my RGB.
       */
      public int getRedValue()
      {
         return this.redValue;
      }

      /**
       * Provide the green value of my RGB.
       *
       * @return Green portion of my RGB.
       */
      public int getGreenValue()
      {
         return this.greenValue;
      }

      /**
       * Provide the blue value of my RGB.
       *
       * @return Blue portion of my RGB.
       */
      public int getBlueValue()
      {
         return this.blueValue;
      }

      /**
       * Provide my RGB settings in String format: (r,g,b).
       *
       * @return String version of my RGB values: (r, g, b).
       */
      public String getRgbString()
      {
         return "(" + this.redValue + ", " + this.greenValue + ", " + this.blueValue + ")";
      }
   }
}



EnumCollections.java

package dustin.examples;

import java.util.EnumMap;
//import java.util.HashMap;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;

import static java.lang.System.out;

/**
 * Simple demonstration of EnumMap and EnumSet.
 *
 * http://download.oracle.com/docs/cd/E17409_01/javase/6/docs/api/java/util/EnumMap.html
 * http://download.oracle.com/docs/cd/E17409_01/javase/6/docs/api/java/util/EnumSet.html
 *
 * @author Dustin
 */
public class EnumCollections
{
   /** Preferred form feed/carriage return/line feed. */
   private static final String NEW_LINE = System.getProperty("line.separator");

   /** Demonstrate the EnumMap in action. */
   public static void demonstrateEnumMap()
   {
//      final Map<ColorEnum, String> colorsAndCauses = new HashMap<ColorEnum, String>();
      final Map<ColorEnum, String> colorsAndAwarenessRibbons = new EnumMap<ColorEnum, String>(ColorEnum.class);
      colorsAndAwarenessRibbons.put(ColorEnum.BLUE, "Bipolar");
      colorsAndAwarenessRibbons.put(ColorEnum.GRAY, "Diabetes");
      colorsAndAwarenessRibbons.put(ColorEnum.PINK, "Breast Cancer");
      colorsAndAwarenessRibbons.put(ColorEnum.RED, "Acquired Immunodeficiency Syndrome");
      colorsAndAwarenessRibbons.put(ColorEnum.YELLOW, "Support the Troops");
      displayMap("Awareness Ribbons Colors", colorsAndAwarenessRibbons);

      // The EnumMap can also be constructed by passing another EnumMap to one
      // of its constructors or by passing a general Map to one of its
      // constructors.
   }

   /** Demonstrate the EnumSet in action. */
   public static void demonstrateEnumSet()
   {
      // instantiate empty EnumSet and fill it individually
      final Set<ColorEnum> denverBroncosColors = EnumSet.noneOf(ColorEnum.class);
      denverBroncosColors.add(ColorEnum.BLUE);
      denverBroncosColors.add(ColorEnum.ORANGE);
      displaySet("Denver Broncos Colors", denverBroncosColors);

      // instantiate EnumSet with single value
      final Set<ColorEnum> orangeFruitColor = EnumSet.of(ColorEnum.ORANGE);
      displaySet("What Color is an Orange?", orangeFruitColor);

      // instantiate EnumSet with two values
      final Set<ColorEnum> greeceFlagColors = EnumSet.of(ColorEnum.BLUE, ColorEnum.WHITE);
      displaySet("Colors of Greek Flag", greeceFlagColors);

      // instantiate EnumSet with three values
      final Set<ColorEnum> usFlagColors =
         EnumSet.of(ColorEnum.RED, ColorEnum.WHITE, ColorEnum.BLUE);
      displaySet("Colors of United States Flag", usFlagColors);

      // instantiate EnumSet with four values
      final Set<ColorEnum> googleIconColors =
         EnumSet.of(ColorEnum.BLUE, ColorEnum.RED, ColorEnum.YELLOW, ColorEnum.GREEN);
      displaySet("Google Icon Colors", googleIconColors);

      // instantite EnumSet with five values
      final Set<ColorEnum> olympicsRingsColors =
         EnumSet.of(ColorEnum.BLUE, ColorEnum.YELLOW, ColorEnum.BLACK, ColorEnum.GREEN, ColorEnum.RED);
      displaySet("Olympics Rings Colors", olympicsRingsColors);

      // place all enum choices in this Set
      final Set<ColorEnum> allColors = EnumSet.allOf(ColorEnum.class);
      displaySet("All Available Colors", allColors);

      // Instantiate Set of ColorEnums that are complement to EnumSet of GRAY
      // Note that the graySet passed to the complementOf method must be an
      // EnumSet specifically rather than the more general Set.
      final EnumSet<ColorEnum> graySet = EnumSet.of(ColorEnum.GRAY);
      final Set<ColorEnum> allButGrayColors = EnumSet.complementOf(graySet);
      displaySet("All Colors Except Gray", allButGrayColors);

      final EnumSet<ColorEnum> btogColors = EnumSet.range(ColorEnum.BLACK, ColorEnum.GREEN);
      displaySet("'B' Colors to 'G' Colors", btogColors);
   }

   /**
    * Display the provided Map by writing its contents to standard output with
    * a separating header that includes the provided header text.
    *
    * @param header Header text to demarcate the output.
    * @param mapToDisplay Map whose contents should be written to standard output.
    */
   private static void displayMap(
      final String header, final Map<ColorEnum, String> mapToDisplay)
   {
      out.println(buildHeaderSeparator(header));
      for (final Map.Entry<ColorEnum, String> entry : mapToDisplay.entrySet())
      {
         final ColorEnum color = entry.getKey();
         out.println(color + " is used to represent " + entry.getValue());
         out.println(
              "  (and it is represented in HTML as "
            + color.getHtmlHexadecimalCode() + " and in RGB as "
            + color.getRgbRepresentation() + ".");
      }
   }

   /**
    * Write the provided Set to standard output along with the provided header.
    *
    * @param header Header to be displayed before the set being printed.
    * @param setToDisplay Set to be displayed by writing to standard output.
    */
   private static void displaySet(final String header, final Set<ColorEnum> setToDisplay)
   {
      out.println(buildHeaderSeparator(header));
      out.println(setToDisplay);
   }

   /**
    * Provide a separating header based on the provided header String.
    *
    * @param header The string to be placed in the header.
    * @return Header separator with provided String and separation marks.
    */
   private static String buildHeaderSeparator(final String header)
   {
      return
           NEW_LINE
         + "=================================================================="
         + NEW_LINE
         + "= " + header
         + NEW_LINE
         + "==================================================================";
   }

   /**
    * Main executable function.
    *
    * @param arguments Command-line arguments; none expected.
    */
   public static void main(final String[] arguments)
   {
      demonstrateEnumMap();
      demonstrateEnumSet();
   }
}



As the code demonstrates (or describes in the comments), instances of EnumMap are acquired via one of the overloaded constructors (one of three shown in the example) while instances of EnumSet are acquired via one of the overload static initialization methods (most of which are demonstrated in the example).  In all cases, the specific enum being used is supplied at instantiation time.  As their names imply, EnumSet implements Set and EnumMap implements Map, so they can generally be used anywhere those interfaces apply and generally support the methods and contracts of those interfaces.

Many fellow Java developers have blogged on the virtues of EnumMap and EnumSet.  These include the blog posts Fun with EnumSet, Playing with EnumSet, and Java's EnumSet: Fun for the Whole Family.

No comments: