Monday, November 12, 2012

Java enums are more than constant fields.

An enum is a type which defines set of fixed constants. For example, if you think generally, the seasons of the year, the planets in the solar system can be defined as enums. Also, you can identify constant types  in your application and defined those as enums which gives you much reliability, maintainability and also the flexibility. Enums were introduced to the Java language with 1.5 release which provides great functionality than what we know, and what we are using. Before introducing enums to the language, we used int, string constants which have many shortages. 

With this post, I am going to discuss the advantages of using enums and the shortcomings of using traditional constants approach. Suppose, we want to keep constant types for seasons of the year. Before enum was introduced, we normally defined a constant group as follows. 

 public static final int SEASON_SPRING = 3;
 public static final int SEASON_SUMMER = 6;
 public static final int SEASON_AUTUMN = 9;
 public static final int SEASON_WINTER = 12;
The value assigned for each field is, the starting month of the season. For example, the summer season will start from 6th month of the year. In real world, the starting month of each season will vary from country to country. I used above values just for illustration purpose. 

Let's discuss the shortcomings of the above approach when comparing with enum types. 

Constant fields don't provide type safety. Suppose you have written a method which returns the months belong to each season.

private List<String> getMonthsOfSeason(int seasonStartingMonth) {
  
    switch(seasonStartingMonth) {
       case 3:
           return CalendarUtil.getMonthsOfYear(new int[]{2,3,4});
           //[March, April, May]
       case 6:
           return CalendarUtil.getMonthsOfYear(new int[]{5,6,7});
           //[June, July, August]
       case 9:
           return CalendarUtil.getMonthsOfYear(new int[]{8,9,10});
           //[September, October, November]
       case 12:
           return CalendarUtil.getMonthsOfYear(new int[]{11,0,1});
           //[December, January, February]
       default:
           return new ArrayList<String>(); 
    }
}

If we use traditional int constants approach, we have to implement similar method like above to get the months of a season. I will show you the code for CalendarUtil class later in this post.

The above method accepts any integer value as the season starting month. By mistakenly, you may pass a different integer constant field (some number except 3,6,9,12) into the method instead of passing one of the season number which has been defined as constants above. In that kind of scenario, the method will returns empty list which may result unexpected output.

Though, You can add considerable validation to the above method, still that method is brittle. The other thing is, you may compare the parameter value with any integer since defined constants are integer type.

   
   If(seasonStartingMonth == QUATER_THREE) {
     //some code
   }

In the above code 'QUATER_THREE' is some different integer constant field defined in the same class for different purpose. Our programmer has mistakenly compared 'seasonStartingMonth' variable with QUATER_THREE constant field. But this is valid comparison, no compile error, but it will give wrong output. Programmer actually intended to compare 'seasonStartingMonth' variable with one of the our defined season constant field. 

Java does not provide namespaces to distinguish each constant groups. We have to use prefixes when two int constant groups have identically named fields. I have used SEASON_ for season group for that purpose. 

The int constants are compile-time constants. The int enums are compiled into the clients that use them. If the int associated with an enum constant is changed, its clients must be recompiled. If they aren’t, they will still run, but their behaviour will be undefined. 

The int constants don't give good debugging information. The int constant fields do not give helpful information when you are debugging your code. If you print some int constant field, it will print just a number which does not make sense. There is no easy way to translate int constant fields into printable strings. 

There is no reliable way to iterate over all the constants defined in a particular constant group and also the size of the constant group. 

Above, I have highlighted some of the shortcoming when using traditional constant field approach.

I have small utility class as follows.

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;


public final class CalendarUtil {

    //Returns list of names of the months for a given array of month numbers.
    public static List<String> getMonthsOfYear(int[] months) {
         List<String> monthArray = new ArrayList<String>(3);
         Calendar cal = Calendar.getInstance();

         for (int i = 0; i < months.length; i++) {
             cal.set(Calendar.MONTH, months[i]);
             monthArray.add(cal.getDisplayName(Calendar.MONTH,
                    Calendar.LONG, Locale.ENGLISH));
         }
         return monthArray;
    }
}

In the above utility class, the 'getMonthsOfYear()' method returns list of month's name for a given array of month numbers of the year.

Now,let's try to do the same thing by using enum. The following code shows an enum definition for seasons of the year.

import java.util.ArrayList;
import java.util.List;

public enum Season {

   SEASON_SPRING(3), //[March, April, May]
   SEASON_SUMMER(6), //[June, July, August]
   SEASON_AUTUMN(9), //[December, January, February]
   SEASON_WINTER(12);//[September, October, November]
 
   private final int startingMonth;
 
   SEASON(int startingMonth) {
       this.startingMonth = startingMonth;
   }
 
   public List<String> getMonthsOfSeason() {

       switch(startingMonth) {
           case 3:
             return CalendarUtil.getMonthsOfYear(new int[]{2,3,4});
           case 6:
             return CalendarUtil.getMonthsOfYear(new int[]{5,6,7});
           case 9:
             return CalendarUtil.getMonthsOfYear(new int[]{8,9,10});
           case 12:
             return CalendarUtil.getMonthsOfYear(new int[]{11,0,1});
           default:
             return new ArrayList$lt;String>(); 
       }
    }
}

See how Java enums are smart? Java enums are classes that export one instance for each enumeration constant via a public static final field. This kind of enum declaration provides compile time safety. For example, consider the following method.

private List<String> getMonthsOfSeason(Season season) {

}

This method guaranteed that it won't accept any other types of values except Season. If we try to pass some other types, compiler will point it out as an error. 

Since Java enum has it's own namespace, identically named constants can coexist with out any problem. For example, We can use same enum constant field name with another enum declaration. 

We can easily convert enum constant fields to printable string using toString() method.

Java enum allows us to keep methods and fields associated with it. In the above example, I have declared one field and method with the enum. I have defined the method with the enum it self which returns months of the season. This is very reliable and convenience implementation. The likelihood of happening bugs in the application is comparatively less. If we want to get the months of winter seasion, we can do it as follows.

List<String> months = Season.SEASON_WINTER.getMonthsOfSeason();

In the above example, the defined method behaves similarly for every constant field. But, We can implement method which behaves differently for each constant field on the enum. I have discussed about field specific method implementation with another post. Please have a look on following URL.

http://skillshared.blogspot.com/2012/01/java-enum-constant-specific-method.html

We can associate data with each constant field of a enum. I have specified starting month number with each constant field of Season enum. We can associate data as many with the enum constant field with the declared fields with corresponding types of data and also the constructor parallel to those data. Enums are immutable, so all fields should be final.

Also, if you want to iterate over all the constant fields of the enum, you can do it as follows.

for (Season s : Season.values()) {
   System.out.println("Months of seasion : " + s.toString());
   System.out.println(s.getMonthsOfSeason());
}

I am going to conclude this article with nice example. Consider the eight planets of our solar system. There are tow constant attribute for each planet has, a mass and a radius, and from these two attributes you can compute its surface gravity. This in turn lets you compute the weight of an object on the planet’s surface, given the mass of the object. Here’s how this enum looks.The numbers in parentheses after each enum constant are parameters that are passed to its constructor. In this case, they are the planet’s mass and radius.
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;
    private final double radius;
    private final double surfaceGravity;

    private static final double G = 6.67300E-11;

    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;
   }
}

2 comments:

  1. Indeed, One of the great article to learn Enum. I would also suggest to read through following comprehensive 10 Enum Examples in Java and 15 Java Enum Interview Questions. Both of them provide good overview of different enum features.

    ReplyDelete
  2. Awesome and informative blog, i ilke your post

    ReplyDelete

Share

Widgets