More Complicated Enumerated Types
Since enums are effectively classes, you can do pretty much everything with them that you can do with a class. In particular, you can provide one or more constructors for an enum type! That may seem a little weird to you (it does to me), because you never call an enum constructor anywhere in your code, or use the "new" operator to instantiate an enum. The definition of an enum brings the class enum constants into existence. You always work with static fields of an enum, not instances.
You might want to write a constructor when you have enumerations with a close relationship to numeric values. An example is an enumeration of hens' egg sizes, shown in Table 6-1. In the U.S., these are the names and weights associated with eggs:
Table 6-1. U.S. names and weights associated with eggs
Name |
weight per dozen |
---|---|
Jumbo |
30 oz |
Extra large |
27 oz |
Large |
24 oz |
Adding constructors to your enums
We're going to create an enum class called egg, with enum constants of jumbo, etc. You can tie arbitrary data to each enum constant by writing a constructor for the enum class. In other circumstances, a constructor has the same name as the class, so you'd expect it to be called egg here. And indeed it is, but that's not the full story. You declare an enum constant (jumbo, etc) and that name declaration is regarded as a call to your constructor. You may pass data values as arguments to the call. Arguments are passed in the usual way, by enclosing the comma-separated list in parentheses.
Putting it together, the enum now looks like this:
enum egg { // the enum constants, which "call" the constructor jumbo(30.0), extraLarge(27.0), large(24.0); egg(double w) {weight=w;} // constructor private double weight; }
The beta JDK 1.5 compiler requires the enum constants to come before the constructor. There's no good reason for that and I filed it as a bug, but no word yet on whether Sun sees it the same way. As well as constructors, you can add practically any methods or data fields in an enum class. The "private" keyword makes a member inaccessible from outside the class. So you probably want to add this method to the enum to be able to retrieve the weight of an enum variable:
double getWeight() { return this.weight; }
Here's a small main program that uses the enum, and prints out the weight for jumbo eggs.
public class bigegg { public static void main( String[] args) { egg e = egg.jumbo; double wt = e. getWeight(); System.out.println( e + " eggs weigh "+ wt +" oz. per doz."); } }
Running the code gives this output:
jumbo eggs weigh 30.0oz. per doz.
The language specification guarantees that all enum constants are unique. There may be two different reference variables pointing to Bread.rye, but there are never two different Bread.rye enum constants. That ensures programmers can compare enums using e == Bread.rye as well as e.equals( Bread. rye).
You need to avoid declaring any members in your enums with names that duplicate those in java.lang.Enum. The box below has a list of most of the methods. The compiler will tell you if you make this mistake.
Predefined members that belong to every enum
The compiler automatically creates several other members in addition to values() in every enum class. These additional members help the enum do its work. The compiler will warn you if you re-use one of these names. Furthermore, each enum type is regarded as a child of class java.lang.Enum, and therefore inherits those methods from there too. Some of the methods in java.lang.Enum are:
public int compareTo( Enum e ); // returns negative, 0, positive for this earlier, equal, or later // in declaration order than e public int ordinal(); // returns the position of the enum constant in the // declaration, first enum constant is at position 0 public static EnumType valueOf( java.lang.Class ec, String s ); // turns a String into its corresponding enum constant // the ec argument is the class object of the enum type we are using public boolean equals( Object o ); // returns true when this equals the enum specified by Object o.
Instance methods and enum variables
We mentioned back at the start of the chapter that enum types are implemented as classes, so you can add your own methods to them. Here's an example showing how you can do that:
enum Month { Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec; int days() { if ((this==Sep)|(this==Apr)|(this==Jun)|(this==Nov)) return 30; if (this==Feb) throw new UnsupportedOperationException("need leap year status"); return 31; } }
We added the method days() to enum Month. It returns a count of the number of days in the month, except for Feb when it will throw an exception. That's a lame response, but better than allowing the day count to be wrong. What this says is that the days() method should not be part of the enum Month (in real world code), because it does not have enough information to do its job.
You can declare an enum variable like this:
Month easterMonth;
You can use it like this:
// Easter is always between March 22 and April 25 easterMonth = calcMonthOfEaster(); // this method not shown! if (easterMonth == Month.Mar ) /* do something*/ ; if (easterMonth.days() != 31) /* do something else */ ;
Constant-specific class bodies
There's only one more aspect of enums to cover, and it's something that you will encounter rarely in practice: a feature known as constant-specific class bodies. We've seen previously how enums are implemented in terms of classes, and how they can have their own constructors, methods and data, as well as the enum constants.
Going a step beyond this, each enum constant in an enum type can be given its own class body. You write it as the enum constant name followed by a block like this
{ body_of_a_class }
and anything that can appear in a class can go in that body.
This kind of class body attached to an enum constant is called a constant-specific class body, and it is regarded as an anonymous (cannot be referred to by a name) class that extends the enum type. When a child class extends a parent, it may provide its own version of a method with the same signature in the parent. This process of replacing a method is known as overriding. In particular, an instance method in a constant-specific class body will override an instance method of the same signature that belongs to the enum type. Phew!
Here's an example, building on the basic egg enum:
enum egg { large { /* this is a constant-specific class body*/ }, extraLarge { /* so is this, and they are empty*/ }, jumbo { /* yep, same here*/ }; public double recipeEquivalent() { return 1.0; } }
The example shows three enum constants, each with an empty constant-specific class body. The enum type also has an instance method called recipeEquivalent().
As you may know, when a recipe calls for eggs, it always means large eggs [1] . We are going to override the enum type method recipeEquivalent() and provide a constant specific method that gives the "scaling factor" for jumbo and extra large eggs. To make your recipes come out right, you need to use fractions of eggs when using the bigger eggs. The method recipeEquivalent() provides the scaling factor for each egg size.
Referring back to table 6-1, by weight,
-
1 large egg == (1extra-large egg * 24/ 27) == (1 jumbo egg * 24/ 30).
That can be expressed in the code like this:
enum egg { large, extraLarge { public double recipeEquivalent(){ return 24.0/ 27.0; }}, jumbo { public double recipeEquivalent(){ return 24.0/ 30.0; }}; public double recipeEquivalent() { return 1.0; } }
There are other ways to get the same effect of scaling different egg sizes. This example is meant to show how you might use a constant-specific class body. Here's a main program to test the feature:
public class eggtest { public static void main( String[] args) { System.out.println(" when the recipe calls for 1 egg, use:"); for (egg e : egg.values() ) { System.out.printf("%f %s eggs %n", e.recipeEquivalent(), e ); } } }
The result of compiling and running that code is:
when the recipe calls for 1 egg, use: 1.00 large eggs 0.88 extraLarge eggs 0.80 jumbo eggs
Note the use of the new printf() method. Chapter 17 has the full details. Remember to add an option on the command line to compile these new features:
javac -source 1.5 egg.java eggtest.java
Finally, if your enums start to sprout constructors, methods, constant specific bodies and instance data, maybe your enum type should just be a class in the first place.