Polymorphic Enums in J2SE 5.0 (Tiger)

Java version 5.0 introduces many dramatic new language features. This article further discusses the new enum facility, demonstrating how you can define enum constants polymorphically. You may want to first read the article Typesafe Enum: Using enum in J2SE 5.0 (Tiger).

The Grade enum

In my first article on enum, I introduced a Grade enum. Here is the code:

enum Grade {
   A("great"),
   B("good"),
   C("ok"),
   D("eh"),
   F("loser");

   private String message;

   Grade(String message) {
      this.message = message;
   }

   String getMessage() {
      return message;
   }
}

Suppose you want the ability to determine whether a test score falls into a specific grade. A score of 90 up through 100 is an A; an 80 up to but not including 90 is a B, and so on. A score of less than (but not including) 60 warrants an F.

You can modify the Grade enum to take a lower and upper range for each grade. You can then provide a method (includes) to determine whether or not a score maps to the current Grade enum:

public enum Grade {
   A("great", 90.0, 100.0),
   B("good", 80.0, 90.0),
   C("ok", 70.0, 80.0),
   D("eh", 60.0, 70.0),
   F("loser", 0.0, 60.0);

   private String message;
   private double from;
   private double to;

   Grade(String message, double from, double to) {
      this.message = message;
      this.from = from;
      this.to = to;
   }

   String getMessage() {
      return message;
   }

   boolean includes(double score) {
      return from <= score && score < to;
   }
}

So far, nothing new. The only problem is that it won't work. The to value represents the top end of the grade range and must be exclusive--except for an A. A perfect score of 100 must report back as an A, but it does not with the above code.

One solution would be to include special logic in the definition of enum, or perhaps modify the constructor to take a boolean representing whether or not the range upper bound is inclusive.

Another solution is to have the declaration of the Grade.A enum constant override the definition for includes.

public enum Grade {
   A("great", 90.0, 100.0) {
      boolean includes(double score) {
         return super.includes(score) || score == to;
      }
   },
   B("good", 80.0, 90.0),
   C("ok", 70.0, 80.0),
   D("eh", 60.0, 70.0),
   F("loser", 0.0, 60.0);

   private String message;
   private double from;
   protected double to;

   Grade(String message, double from, double to) {
      this.message = message;
      this.from = from;
      this.to = to;
   }

   String getMessage() {
      return message;
   }

   boolean includes(double score) {
      return from <= score && score < to;
   }
}

You supply the new definition for includes inline, just as if you were overriding a method in an inner class declaration.

Note that you must also define the to variable as protected in order for this to work.

If you want a different implementation for an enum method for each and every enum constant, you can declare both the method and the enum type as abstract.