Enum Constant Specific Body in Java

In an enum it is possible to define a specific behavior for a particular constant of the enum which overrides the default behavior of the enum, this technique is known as constant specific body.

Suppose three piano students – John, Ben and Luke – are defined in an enum named PianoClass, as follows:

enum PianoClass {
JOHN, BEN, LUKE;
     public String getSex() {
         return "Male";
     }
     public String getLevel() {
         return "Beginner";
     }
}
And one day two other students arrive - Rita and Tom - with a sex (Female) and level (Intermediate) that do not match the previous ones:
enum PianoClass2 {
JOHN, BEN, LUKE, RITA, TOM;
    public String getSex() {
         return "Male"; // issue, Rita is a female
    }
    public String getLevel() {
        return "Beginner"; // issue, Tom is an intermediate student
   }
}

So that simply adding the new students to the constant declaration, as follows, is not correct:

PianoClass2 tom = PianoClass2.TOM;
PianoClass2 rita = PianoClass2.RITA;
System.out.println(tom.getLevel());  // prints Beginner -> wrong Tom's not a beginner
System.out.println(rita.getSex());   // prints Male -> wrong Rita's not a male

It’s possible to define a specific behavior for each of the constant, Rita and Tom, which overrides the PianoClass2 default behavior as follows:

enum PianoClass3 {
    JOHN, BEN, LUKE,
    RITA {
         @Override
         public String getSex() {
              return "Female";
         }
},
    TOM {
        @Override
        public String getLevel() {
            return "Intermediate";
       }
};
       public String getSex() {
           return "Male";
       }
      public String getLevel() {
           return "Beginner";
      }
}

and now Tom’s level and Rita’s sex are as they should be:

PianoClass3 tom = PianoClass3.TOM;
PianoClass3 rita = PianoClass3.RITA;
System.out.println(tom.getLevel()); // prints Intermediate
System.out.println(rita.getSex()); // prints Female

Another way to define content specific body is by using constructor, for instance:

enum Friend {
     MAT("Male"),
     JOHN("Male"),
     JANE("Female");

     private String gender;

     Friend(String gender) {
         this.gender = gender;
     }
     public String getGender() {
         return this.gender;
     }
}

and usage:

Friend mat = Friend.MAT;
Friend john = Friend.JOHN;
Friend jane = Friend.JANE;
System.out.println(mat.getGender()); // Male
System.out.println(john.getGender()); // Male
System.out.println(jane.getGender()); // Female

Getting the values of an enum

Each enum class contains an implicit static method named values(). This method returns an array containing all values of that enum. You can use this method to iterate over the values. It is important to note however that this method returns a new array every time it is called.

Related Article: Java Enums Tutorial with Examples

public enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;

    /**
    *Print out all the values in this enum.
    */
    public static void printAllDays() {
        for(Day day : Day.values()) {
              System.out.println(day.name());
        }
    }
}

If you need a Set you can use EnumSet.allOf(Day.class) as well.

Enum Polymorphism Pattern

When a method need to accept an “extensible” set of enum values, the programmer can apply polymorphism like on a normal class by creating an interface which will be used anywere where the enums shall be used:

public interface ExtensibleEnum {
     String name();
}

This way, any enum tagged by (implementing) the interface can be used as a parameter, allowing the programmer to create a variable amount of enums that will be accepted by the method. This can be useful, for example, in APIs
where there is a default (unmodifiable) enum and the user of these APIs want to “extend” the enum with more values.

A set of default enum values can be defined as follows:

public enum DefaultValues implements ExtensibleEnum {
VALUE_ONE, VALUE_TWO;
}

Additional values can then be defined like this:

public enum ExtendedValues implements ExtensibleEnum {
VALUE_THREE, VALUE_FOUR;
}

Sample which shows how to use the enums – note how printEnum() accepts values from both enum types:

private void printEnum(ExtensibleEnum val) {
     System.out.println(val.name());
}

printEnum(DefaultValues.VALUE_ONE);    // VALUE_ONE
printEnum(DefaultValues.VALUE_TWO);    // VALUE_TWO
printEnum(ExtendedValues.VALUE_THREE); // VALUE_THREE
printEnum(ExtendedValues.VALUE_FOUR);  // VALUE_FOUR

Note: This pattern does not prevent you from redefining enum values, which are already defined in one enum, in another enum. These enum values would be different instances then. Also, it is not possible to use switch-on-enum since all we have is the interface, not the real enum.

Compare and Contains for Enum values

Enums contains only constants and can be compared directly with ==. So, only reference check is needed, no need to use .equals method. Moreover, if .equals used incorrectly, may raise the NullPointerException while that’s
not the case with == check.

enum Day {
     GOOD, AVERAGE, WORST;
}
public class Test {
    public static void main(String[] args) {
         Day day = null;
         if (day.equals(Day.GOOD)) {//NullPointerException!
             System.out.println("Good Day!");
         }
         if (day == Day.GOOD) {//Always use == to compare enum
             System.out.println("Good Day!");
         }
   }
}

To group, complement, range the enum values we have EnumSet class which contains different methods.

  • EnumSet#range : To get subset of enum by range defined by two endpoints
  • EnumSet#of : Set of specific enums without any range. Multiple overloaded of methods are there.
  • EnumSet#complementOf : Set of enum which is complement of enum values provided in method parameter
enum Page {
         A1, A2, A3, A4, A5, A6, A7, A8, A9, A10
}
public class Test {
       public static void main(String[] args) {
              EnumSet range = EnumSet.range(Page.A1, Page.A5);
             if (range.contains(Page.A4)) {
                    System.out.println("Range contains A4");
      }
   
      EnumSet of = EnumSet.of(Page.A1, Page.A5, Page.A3);
      if (of.contains(Page.A1)) {
             System.out.println("Of contains A1");
      }
   }
}

Get enum constant by name

Say we have an enum DayOfWeek:

enum DayOfWeek {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
}

An enum is compiled with a built-in static valueOf() method which can be used to lookup a constant by its name:

String dayName = DayOfWeek.SUNDAY.name();
assert dayName.equals("SUNDAY");

DayOfWeek day = DayOfWeek.valueOf(dayName);
assert day == DayOfWeek.SUNDAY;

This is also possible using a dynamic enum type:

Class enumType = DayOfWeek.class;
DayOfWeek day = Enum.valueOf(enumType, “SUNDAY”);
assert day == DayOfWeek.SUNDAY;

Both of these valueOf() methods will throw an IllegalArgumentException if the specified enum does not have a constant with a matching name.

The Guava library provides a helper method Enums.getIfPresent() that returns a Guava Optional to eliminate explicit exception handling:

DayOfWeek defaultDay = DayOfWeek.SUNDAY;
DayOfWeek day = Enums.valueOf(DayOfWeek.class, "INVALID").or(defaultDay);
assert day == DayOfWeek.SUNDAY;

Enum with properties (fields)

In case we want to use enum with more information and not just as constant values, and we want to be able to compare two enums.

Consider the following example:

public enum Coin {
    PENNY(1), NICKEL(5), DIME(10), QUARTER(25);

    private final int value;
    Coin(int value){
         this.value = value;
    }
    public boolean isGreaterThan(Coin other){
        return this.value > other.value;
    }
}

Here we defined an Enum called Coin which represent its value. With the method isGreaterThan we can compare two enums:

Coin penny = Coin.PENNY;
Coin dime = Coin.DIME;
System.out.println(penny.isGreaterThan(dime)); // prints: false
System.out.println(dime.isGreaterThan(penny)); // prints: true
Convert enum to String

Sometimes you want to convert your enum to a String, there are two ways to do that.
Assume we have:

public enum Fruit {
APPLE, ORANGE, STRAWBERRY, BANANA, LEMON, GRAPE_FRUIT;
}

So how do we convert something like Fruit.APPLE to “APPLE”?

Convert using name()

name() is an internal method in enum that returns the String representation of the enum, the return String represents exactly how the enum value was defined.

For example:

System.out.println(Fruit.BANANA.name());      // "BANANA"
System.out.println(Fruit.GRAPE_FRUIT.name()); // "GRAPE_FRUIT"

Convert using toString()

toString() is, by default, overridden to have the same behavior as name()

However, toString() is likely overridden by developers to make it print a more user friendly String

Don’t use toString() if you want to do checking in your code, name() is much more stable for that. Only use toString() when you are going to output the value to logs or stdout or something

By default:
System.out.println(Fruit.BANANA.toString());      // "BANANA"
System.out.println(Fruit.GRAPE_FRUIT.toString()); // "GRAPE_FRUIT"

Example of being overridden
System.out.println(Fruit.BANANA.toString());      // "Banana"
System.out.println(Fruit.GRAPE_FRUIT.toString()); // "Grape Fruit"
Enums with static fields

If your enum class is required to have static fields, keep in mind they are created after the enum values themselves. That means, the following code will result in a NullPointerException:

enum Example {
    ONE(1), TWO(2);

   static Map integers = new HashMap<>();

   private Example(int value) {
      integers.put(this.name(), value);
   }
}

A possible way to fix this:

enum Example {
     ONE(1), TWO(2);
     static Map integers;
     private Example(int value) {
         putValue(this.name(), value);
     }
     private static void putValue(String name, int value) {
         if (integers == null)
              integers = new HashMap<>();
         integers.put(name, value);
     }
}

Do not initialize the static field:

enum Example {
      ONE(1), TWO(2);
      // after initialisisation integers is null!!
      static Map integers = null;
      
      private Example(int value) {
           putValue(this.name(), value);
      }

      private static void putValue(String name, int value) {
           if (integers == null)
               integers = new HashMap<>();
           integers.put(name, value);
     }
     // !!this may lead to null poiner exception!!
     public int getValue(){
           return (Example.integers.get(this.name()));
     }
}

initialisisation:

  • create the enum values
    • as side effect putValue() called that initializes integers
  • the static values are set
    • integers = null; // is executed after the enums so the content of integers is lost

LEAVE A REPLY

Please enter your comment!
Please enter your name here