Enums or enumerated types basically means a type that can be defined to have a certain set of fixed values as per the problem domain. Historically, enums (via enum keyword and any associated semantics) were missing from the featureset provided by versions upto Java 1.4.
However developers tried to achieve the "same enum effect" via something like this:
Example 1:
The above can be avoided if we accept that Enumerations should be treated as a separate type. Implementing them as a sequence of integers is not helpful. To define enumerations as their own type, you do the following:
1. Replace the primitive ints above with 'static final' object references to the same class defining the enumerated constants.
2. Disallow any object creation of the class via a private constructor.
Example 2:
Secondly the acceptable values of Currency can only be defined inside the class due to the private constructor.This along with the fact that Currency is a final class ensures that Currency.USD, Currency.EUR, Currency.GBP and Currency.YEN are the only instances of the Currency class.
It also means that we can use the identity comparison (==) operator instead of the equals() method when comparing enum values. Identity comparison (==) is always faster than equals since we are only comparing object references in the former as opposed to object values in the latter.
However the above typesafety approach comes with the following disadvantages:
1. The above implementation is not Serializable and Comparable by default. It means we can have issues using them in the context of RMI and EJBs. In case, if we make them Serializable, constructing the object again creates a new instance of the same Currency by ignoring its private constructor completely and does not retrieve the same instance that was serialized. This means == comparison fails to identify the equality of a serialized and a non-serialized Currency. Also it means we no longer have a unique single instance of the currency type. We have to implement more boiler-plate code like implementing the readResolve method as suggested in http://www.javaworld.com/javaworld/javatips/jw-javatip122.html?page=2.
2. We cannot switch over the above enum values (remember it is easier to switch over ints) to get any business logic done. If we need to switch, it can be facilitated by providing a getValue() method that returns the int value.
Example 3:
Inside Currency class,
1. All defined enums implicitly extend from java.lang.Enum just as all objects implicitly extend from java.lang.Object.
2. The above feature taks care of default implementation for toString(), equals(), hashCode() methods.
3. They are Serializable and Comparable by default without generating duplicate instances during deserialization
4. They can be used in switch-case statements.
5. They can have behavior ( via member variables , methods , constructors, interface implementations etc) in addition to just specifying the constants.
Simplest example of Java 5 enum class with no additional behavior.
Example 4:
We can iterate through all the instances of Currency via the static values() method and take advantage of the toString() method in the print statement.
Example 5:
We can also add behavior via member fields and methods.
When you need to provide custom behavior based on the enum values, there are 2 ways of doing it. Either you switch case based on the enum values in the application code or a yet better way is to move the custom logic inside the enum class as follows:
Example 6:
Switching over case statements could be used if you have no option of modifying the enum class code. This can happen in cases where the enum class is generated - e.g. using JAXB - from an XSD. More on this in a later blog.
So this covers the basics of Java 5 Enums. Java 5 also provides 2 data structures - java.util.EnumSet and java.util.EnumMap. More on this again will be in yet another blog.
However developers tried to achieve the "same enum effect" via something like this:
Example 1:
public class Currency {
public static final int USD = 1;
public static final int EUR = 2;
public static final int GBP = 3;
public static final int YEN = 4;
}
public class CurrencyConverter {
public void convertCurrency(int fromCurrency, int toCurrency) { ... }
public static void main(String[] args) {
CurrencyConverter cc = new CurrencyConverter();
cc.convertCurrency(Currency.USD, Currency.YEN);
}
}
Additional currencies could be added to the Currency class by defining new constants. However, the convertCurrency (int, int) method lacks typesafety since the method signature indicates it can accept any int. However, the only acceptabe range of ints is 1 through 4. If we call the method outside of the range of agreed upon constants , e.g. convertCurrency(8, 10), the program fails. The above can be avoided if we accept that Enumerations should be treated as a separate type. Implementing them as a sequence of integers is not helpful. To define enumerations as their own type, you do the following:
1. Replace the primitive ints above with 'static final' object references to the same class defining the enumerated constants.
2. Disallow any object creation of the class via a private constructor.
Example 2:
public final class Currency {
public static final Currency USD = new Currency(1);
public static final Currency EUR = new Currency(2);
public static final Currency GBP = new Currency(3);
public static final Currency YEN = new Currency(4);
int value;
private Currency(int value){
this.value = value;
}
}
public class CurrencyConverter {
public void convertCurrency(Currency fromCurrency, Currency toCurrency) { ... }
public static void main(String[] args) {
CurrencyConverter cc = new CurrencyConverter();
cc.convertCurrency(Currency.USD, Currency.YEN);
}
}
The convertCurrency(Currency, Currency) now takes the Currency type instead of an int.Secondly the acceptable values of Currency can only be defined inside the class due to the private constructor.This along with the fact that Currency is a final class ensures that Currency.USD, Currency.EUR, Currency.GBP and Currency.YEN are the only instances of the Currency class.
It also means that we can use the identity comparison (==) operator instead of the equals() method when comparing enum values. Identity comparison (==) is always faster than equals since we are only comparing object references in the former as opposed to object values in the latter.
However the above typesafety approach comes with the following disadvantages:
1. The above implementation is not Serializable and Comparable by default. It means we can have issues using them in the context of RMI and EJBs. In case, if we make them Serializable, constructing the object again creates a new instance of the same Currency by ignoring its private constructor completely and does not retrieve the same instance that was serialized. This means == comparison fails to identify the equality of a serialized and a non-serialized Currency. Also it means we no longer have a unique single instance of the currency type. We have to implement more boiler-plate code like implementing the readResolve method as suggested in http://www.javaworld.com/javaworld/javatips/jw-javatip122.html?page=2.
2. We cannot switch over the above enum values (remember it is easier to switch over ints) to get any business logic done. If we need to switch, it can be facilitated by providing a getValue() method that returns the int value.
Example 3:
Inside Currency class,
public class Currency {
....
public int getValue() {
return value;
}
}
public class CurrencyConverter {
public void convertCurrency(Currency fromCurrency, Currency toCurrency) { ... }
}
Java 5 enums are a typesafe feature and overcome all the above problems listed with the Enumerated pattern. In addition to facilitating a way to list a set of constant values, they also provide features such as :1. All defined enums implicitly extend from java.lang.Enum
2. The above feature taks care of default implementation for toString(), equals(), hashCode() methods.
3. They are Serializable and Comparable by default without generating duplicate instances during deserialization
4. They can be used in switch-case statements.
5. They can have behavior ( via member variables , methods , constructors, interface implementations etc) in addition to just specifying the constants.
Simplest example of Java 5 enum class with no additional behavior.
Example 4:
public enum Currency { USD,GBP,EUR,YEN }
public class CurrencyConverter
{
public void printCurrencies() {
for (Currency currency : Currency.values()) {
System.out.println(currency);
System.out.println(currency.ordinal());
}
}
}
Currency is an enum type, and all the above enum values, viz USD, GBP, EUR, YEN are of type Currency.We can iterate through all the instances of Currency via the static values() method and take advantage of the toString() method in the print statement.
Example 5:
We can also add behavior via member fields and methods.
public enum Currency
{
USD("United States"),
GBP("United Kingdom"),
EUR("Europe"),
YEN("Japan")
String country;
public Currency(String country){
this.country = country;
}
public Currency getCurrencyForCountry(String country) {
return Currency.valueOf(country);
}
}
When you need to provide custom behavior based on the enum values, there are 2 ways of doing it. Either you switch case based on the enum values in the application code or a yet better way is to move the custom logic inside the enum class as follows:
Example 6:
public class Client
{
enum HttpStatusCode
{
HTTP200("HTTP 200") {
@Override
void printMessage() {
System.out.println("Successful Transaction ");
}
},
HTTP401("HTTP 401 Error") {
@Override
void printMessage() {
System.out.println("Authentication Failure");
}
},
HTTP404("HTTP 404 Error") {
@Override
void printMessage() {
System.out.println("Requested resource not found at specified location");
}
},
HTTP500("HTTP 500 Error") {
@Override
void printMessage() {
System.out.println("An error occured on server-side, please have patience.");
}
};
String statusString;
HttpStatusCode(String statusString) {
this.statusString = statusString;
}
abstract void printMessage();
}
public static void main(String[] args)
{
HttpStatusCode status = connectToServer();
status.printMessage();
}
}
Switching over case statements could be used if you have no option of modifying the enum class code. This can happen in cases where the enum class is generated - e.g. using JAXB - from an XSD. More on this in a later blog.
So this covers the basics of Java 5 Enums. Java 5 also provides 2 data structures - java.util.EnumSet and java.util.EnumMap. More on this again will be in yet another blog.