Item 30 - Use enums instead of int constants

From Effective Java 2/e by Joshua Bloch

enumerated type
a type whose legal values consist of a fixed set of constants

int enum pattern

// The int enum pattern - severely deficient!
public static final int APPLE_FUJI           = 0;
public static final int APPLE_PIPPIN         = 1;
public static final int APPLE_GRANNY_SMITH   = 2;

public static final int ORANGE_NAVEL         = 0;
public static final int ORANGE_TEMPLE        = 1;
public static final int ORANGE_BLOOD         = 2;

Shortcomings * Provides nothing in the way of type safety and little in the way of convenience * Java doesn’t provide namespaces for int enum groups * Programs that use the int enum pattern are brittle * There is no easy way to translate int enum constants into printable strings

Enum Type

public enum Apple  { FUJI, PIPPIN, GRANNY_SMITH }
public enum Orange { NAVEL, TEMPLE, BLOOD }

Java’s enum types are full-fledged classes, far more powerful than other languages * Enum types are effectively final * By virtue of having no accessible constructors * Cannot create instances of an enum type nor extend it * Enum types are instance-controlled and generalization of singletons * Provide compile-time type safety * Enum types let you add arbitrary methods and fields and implement arbitrary interfaces

// Enum type with data and behavior
public enum Planet {
   MERCURY(3.302e+23, 2.439e6),
   VENUS  (4.869e+24, 6.052e6),
   EARTH  (5.975e+24, 6.378e6),
   MARS   (6.419e+23, 3.393e6),
   JUPITER(1.899e+27, 7.149e7),
   SATURN (5.685e+26, 6.027e7),
   URANUS (8.683e+25, 2.556e7),
   NEPTUNE(1.024e+26, 2.477e7);

   private final double mass;           // In kilograms
   private final double radius;         // In meters
   private final double surfaceGravity; // In m / s^2

   // Universal gravitational constant in m^3 / kg s^2
   private static final double G = 6.67300E-11;

   // Constructor
   Planet(double mass, double radius) {
      this.mass = mass;
      this.radius = radius;
      surfaceGravity = G * mass / (radius * radius);
   }

   public double mass()           { return mass; }
   public double radius()         { return radius; }
   public double surfaceGravity() { return surfaceGravity; }

   public double surfaceWeight(double mass) {
      return mass * surfaceGravity;  // F = ma
   }
}

To associate data with enum constants, declare instance fields and write a constructor that takes the data and stores it in the fields.

Enums are by their nature immutable, so all fields should be final (Item 15). They can be public, but it is better to make them private and provide public accessors (Item 14)

static values method
Static values() method that returns an array of its values in the order they were declared.

toString
toString() method returns the declared name of each enum value. If you’re dissatisfied with this string representation, you can change it by overriding the toString method.

Case which you need to associate fundamentally different behavior with each constant such as the operations on a basic four function calculator

// Enum type that switches on its own value - questionable
public enum Operation {
   PLUS, MINUS, TIMES, DIVIDE;
   // Do the arithmetic op represented by this constant
   double apply(double x, double y) {
      switch(this) {
         case PLUS:   return x + y;
         case MINUS:  return x - y;
         case TIMES:  return x * y;
         case DIVIDE: return x / y;
      }
      throw new AssertionError("Unknown op: " + this);
   }
}

// Enum type with constant-specific method implementations
public enum Operation {
   PLUS   { double apply(double x, double y){return x + y;} },
   MINUS  { double apply(double x, double y){return x - y;} },
   TIMES  { double apply(double x, double y){return x * y;} },
   DIVIDE { double apply(double x, double y){return x / y;} };
   abstract double apply(double x, double y);
}

Switches code is not pretty and also fragile

toString and fromString

// Enum type with constant-specific class bodies and data
public enum Operation {
   PLUS("+") {
      double apply(double x, double y) { return x + y; }
   },
   MINUS("-") {
      double apply(double x, double y) { return x - y; }
   },
   TIMES("*") {
      double apply(double x, double y) { return x * y; }
   },
   DIVIDE("/") {
      double apply(double x, double y) { return x / y; }
   };

   abstract double apply(double x, double y);

   private final String symbol;

   Operation(String symbol) {
      this.symbol = symbol;
   }

   @Override
   public String toString() {
      return symbol;
   }

   // Implementing a fromString method on an enum type
   private static final Map<String, Operation> stringToEnum = new HashMap<String, Operation>();
   static { // Initialize map from constant name to enum constant
      for (Operation op : values())
         stringToEnum.put(op.toString(), op);
   }
   // Returns Operation for string, or null if string is invalid
   public static Operation fromString(String symbol) {
      return stringToEnum.get(symbol);
   }
}
// Enum that switches on its value to share code - questionable
enum PayrollDay {
   MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
   private static final int HOURS_PER_SHIFT = 8;

   double pay(double hoursWorked, double payRate) {
      double basePay = hoursWorked * payRate;
      double overtimePay; // Calculate overtime pay
      switch(this) {
         case SATURDAY:
         case SUNDAY:
            overtimePay = hoursWorked * payRate / 2;
            break;
         default: // Weekdays
            overtimePay = hoursWorked <= HOURS_PER_SHIFT ?
            0 : (hoursWorked - HOURS_PER_SHIFT) * payRate / 2;
            break;
      }
      return basePay + overtimePay;
   }
}

// The strategy enum pattern
enum PayrollDay {
   MONDAY(PayType.WEEKDAY),
   TUESDAY(PayType.WEEKDAY),
   WEDNESDAY(PayType.WEEKDAY),
   THURSDAY(PayType.WEEKDAY),
   FRIDAY(PayType.WEEKDAY),
   SATURDAY(PayType.WEEKEND),
   SUNDAY(PayType.WEEKEND);

   private final PayType payType;
   PayrollDay(PayType payType) { this.payType = payType; }

   double pay(double hoursWorked, double payRate) {
      return payType.pay(hoursWorked, payRate);
   }

   // The strategy enum type
   private enum PayType {
      WEEKDAY {
         double overtimePay(double hours, double payRate) {
            return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT) * payRate / 2;
         }
      },
      WEEKEND {
         double overtimePay(double hours, double payRate) {
            return hours * payRate / 2;
         }
      };

      private static final int HOURS_PER_SHIFT = 8;
      abstract double overtimePay(double hrs, double payRate);

      double pay(double hoursWorked, double payRate) {
         double basePay = hoursWorked * payRate;
         return basePay + overtimePay(hoursWorked, payRate);
      }
   }
}

Posted by The Finest Artist