Sunday, April 12, 2015

Overview of Enum in Java

As per the doc "An enum type is a special data type that enables for a variable to be a set of predefined constants. The variable must be equal to one of the values that have been predefined for it. " There are various examples for it:
  • List of directions e.g. east, west, north and south
  • Set of Suits in a deck of card: clubs, diamonds, spades, hearts.
  • Rank in a deck of card: ace, duce, three etc.
  • Supported currency in a stock application: dollar, rupee, yen etc.
  • Supported operating systems by a backup application: windows, unix, ubuntu etc.
Consider the following enum which lists the supported operating systems for a product:
public enum SupportedOS {
    WINDOW, UNIX;
}

The concerned product can be installed only on windows and unix. This may be used by product installer class as:
public class ProductInstaller {
   SupportedOS supportedOS;

    public ProductInstaller(SupportedOS supportedOS) {
        this.supportedOS = supportedOS;
    }

    public void install() {
        switch (supportedOS) {
            case WINDOW:
                installRoutineForWindows();
                break;
            case UNIX:
                installRoutineForUnix();
                break;
        }
    }

    private void installRoutineForUnix() {
        // code for installation for Unix
    }

    private void installRoutineForWindows() {
        // code for installation for Windows
    }
}

Where should I use an Enum?
When a variable can take one of the values from a small set of predefined values then using an Enum may make sense. One classical example of using enum is writing thread-safe singleton as mentioned in Effective Java.

What is an EnumSet?
If we use enums and want to allows a set of values then we should use EnumSet but all the values must come from a single enum that is specified when the set is created.

Why should I prefer an Enum?
Suppose in the above example we are passing the OS in string form:
public class ProductInstaller {
   String supportedOS;

    public ProductInstaller(String supportedOS) {
        this.supportedOS = supportedOS;
    }

    public void install() {
        switch (supportedOS) {
            case "WINDOW":
                installRoutineForWindows();
                break;
            case "UNIX":
                installRoutineForUnix();
                break;
        }
    }

    private void installRoutineForUnix() {
        // code for installation for Unix
    }

    private void installRoutineForWindows() {
        // code for installation for Windows
    }
}

Now user can pass even an operation system for which product has no installation support, worse (s)he can pass any random string. So using an Enum provides
  • provides type safety and better compile time error checking. 
  • passing an enum as parameter makes it self-documented in sense that one of the possible values from that enum can only be passed.
  • also avoid errors in passing invalid constants. We can also document legal use cases to use with this enum.
  • By using an enum we can print more sensible value if we override toSring method in it.
  • also helpful in case of auto completion when using an IDE e.g. IntelliJ IDEA.
  • when we create an enum compiler also adds some helpful methods e.g. values that returns an array containing all of the values of the enum in the order they are declared.
Why an enum cannot extend anything else?
Because all enums explicitly extend java.lang.enum and a class can only extend one class in Java.

How enum differs from class?
Enum is similar to a regular class but it always has private constructor. We also get some additional supporting methods like values, valueof etc. Also intent with an enum is different from a class as we want to have fixed number of instances in case of an enum but not in case of a class.

Are fields of an enum implicitly final?
No they are not. Consider the following example:
public class MainTest {
    enum Product {
        PRODUCT_ONE(1.1),PRODUCT_TWO(2.1),PRODUCT_THREE(3.1);

        private double version;

        Product(double version) {
            this.version = version;
        }

        @Override
        public String toString() {
            switch (this) {
                case PRODUCT_ONE:
                    System.out.println("Version of PRODUCT_ONE: " + version);
                    break;
                case PRODUCT_TWO:
                    System.out.println("Version of PRODUCT_TWO: " + version);
                    break;
                case PRODUCT_THREE:
                    System.out.println("Version of PRODUCT_THREE: " + version);
                    break;
            }
            return super.toString();
        }
    }

    public static void main(String[] args) {
        System.out.println(Product.PRODUCT_ONE);
        Product.PRODUCT_ONE.version = 1.3;
        System.out.println(Product.PRODUCT_ONE);
    }
}

The output would be:
Version of PRODUCT_ONE: 1.1
PRODUCT_ONE
Version of PRODUCT_ONE: 1.3
PRODUCT_ONE

Generally we would not create mutable enums but some examples are still there e.g. lazy initialization (when we need to compute some field value when they are first used), a regular singleton object e.g. Registry etc. In most cases enum objects would be immutable and their fields be final. We can also use reflection to examine enum.

Why an enum cannot have public or protected constructor?
An enum must have package-private or private access constructor. We can think Enum as a class with fixed number of instances and the number is not going to change at run time. We provide public or protected constructors when we allow more instances to be created and now it seems to make sense that we do not need public or protected constructors. An enum automatically creates the constants at the beginning of enum body and we cannot invoke an enum constructor our-self.

Can we declare an Enum inside a method?
An enum can be declared inside or outside of a class but not in a method.As per the doc:Nested enum types are implicitly static. It is permissible to explicitly declare a nested enum type to be static. This implies that it is impossible to define a local enum, or to define an enum in an inner class.

Can we mark an Enum final?
As mentioned above all nested enums are implicitly static. But we cannot mark an enum declared outside of a class as any of the following: static, abstract, final, protected or private.

How can we ensure that all enum values are used?
In one of the projects we are using Enum to specify appliance type which can have various possible values e.g. Red Hat Linux, SUSE, Windows etc.
public enum ApplianceType {
    APPLIANCE_TYPE_RHL("LinuxBox"),APPLIANCE_TYPE_SUSE("SuseBox"),APPLIANCE_TYPE_WIN("WinBox"), APPLIANCE_TYPE_UBUNTU("Ubuntu");

    private final String applianceType;

    ApplianceType(String applianceType) {
        this.applianceType = applianceType;
    }

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

And then there is a service which will do processing specific to the appliance. Here is the stripped down version:
public class ApplianceService {
    public static void initializeAppliances(ApplianceType applianceType) {
        if(ApplianceType.APPLIANCE_TYPE_RHL == applianceType) {
            // Do some processing specific to Red Hat Appliance.
        }
        else if(ApplianceType.APPLIANCE_TYPE_SUSE == applianceType) {
            // Do some processing specific to SUSE Appliance.
        }
        else if(ApplianceType.APPLIANCE_TYPE_WIN == applianceType) {
            // Do some processing specific to Windows Appliance.
        }
        System.out.println("Initialization over for appliance type: "+applianceType);
    }

    public static void main(String[] args) {
        initializeAppliances(ApplianceType.APPLIANCE_TYPE_RHL);
    }
}

Now everything is fine till here. Now after some time a new appliance gets introduced e.g. Ubuntu appliance and now we need to scan the code to introduce code for this new kind of appliance. This feels like code smell and needs to be handled. The objective is to ensure that every enum value is used.

The point to observe is every enum should have processing method so we can introduce an abstract method in enum itself and next thing is to ensure that every enum value will provide implementation for that method.
package enums;

public enum ApplianceType {
    APPLIANCE_TYPE_RHL("LinuxBox") {
        @Override
        public void initializeAppliance() {
           // code to initialize RHL.
        }
    },APPLIANCE_TYPE_SUSE("SuseBox") {
        @Override
        public void initializeAppliance() {
            // code to initialize Suse.
        }
    },APPLIANCE_TYPE_WIN("WinBox") {
        @Override
        public void initializeAppliance() {
            // code to initialize Windows.
        }
    }, APPLIANCE_TYPE_UBUNTU("Ubuntu") {
        @Override
        public void initializeAppliance() {
            // code to initialize Ubuntu.
        }
    };

    private final String applianceType;

    ApplianceType(String applianceType) {
        this.applianceType = applianceType;
    }

    // Force them all to implement doProcessing.
    public abstract void initializeAppliance();

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

Now next time we introduce a new appliance type that must also implement the abstract method else it will not compile. Another option is to declare an interface and make sure the enum implements this interface.
public interface ApplianceInitializer {
    public void initializeAppliance();
}

public enum ApplianceType implements ApplianceInitializer{
    APPLIANCE_TYPE_RHL("LinuxBox") {
        @Override
        public void initializeAppliance() {
           // code to initialize RHL.
        }
    },APPLIANCE_TYPE_SUSE("SuseBox") {
        @Override
        public void initializeAppliance() {
            // code to initialize Suse.
        }
    },APPLIANCE_TYPE_WIN("WinBox") {
        @Override
        public void initializeAppliance() {
            // code to initialize Windows.
        }
    }, APPLIANCE_TYPE_UBUNTU("Ubuntu") {
        @Override
        public void initializeAppliance() {
            // code to initialize Ubuntu.
        }
    };

    private final String applianceType;

    ApplianceType(String applianceType) {
        this.applianceType = applianceType;
    }

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

This implementation will also ensure that all values of a enum are used. But this is more flexible in the sense that the interface can be implemented by other classes as well (in case we need it).

Thats all folks! Please leave your feedback in comments.

No comments: