Why Enumerate a Type?
Here's the older approach of simulating enumerations:
class Bread { static final int wholewheat = 0; static final int ninegrain = 1; static final int rye = 2; static final int french = 3; }
You would then declare an int variable and let it hold values from the Bread class, e.g.
int todaysLoaf = Bread.rye;
Drawbacks of using ints to enumerate
Using final ints to represent values in an enumeration has at least three drawbacks.
-
All the compiler tools (debugger, linker, run-time, etc.) still regard the variables as ints. They are ints. If you ask for the value of todaysLoaf, it will be 2, not "rye". The programmer has to do the mapping back and forth mentally.
-
The variables aren't typesafe. There's nothing to stop todaysLoaf getting assigned a value of 99 that doesn't correspond to any Bread value. What happens next depends on how well the rest of your code is written, but the best case is that some routine notices pretty quickly and throws an exception. The worst case is that your computer-controlled bakery tries to bake "type 99" bread causing an expensive sticky mess.
-
Use of integer constants makes code "brittle" (easily subject to breakage). The constants get compiled into every class that use them. If you update the class where the constants are defined, you must go and find all the users of that class, and recompile them against the new definitions. If you miss one, the code will run but be subject to subtle bugs.
How enums solve these issues
Enumerated types were introduced with JDK 1.5 to address these limitations. Variables of enumerated types
-
Are displayed to the programmer or user as Strings, not numbers
-
Can only hold values defined in the type
-
Do not require clients to be recompiled when the enumeration changes
Enumerated types are written using a similar syntax to class declarations, and you should think of them as being a specialized sort of class. An enumerated type definition can go in a file of its own, or in a file with other classes. A public enumeration must be in a file of its own with the name matching the enumeration name.
You might create an enumerated type to represent some bread flavors. It would be defined like this:
enum Bread { wholewheat, ninegrain, rye, french }
That lets you declare variables of type Bread in the usual way:
Bread todaysLoaf;
You can assign an enum value to a variable of Bread type like this:
todaysLoaf = Bread.rye;
All the language tools know about the symbolic names. If you print out a Bread variable, you get the string value, not whatever numeric constant underlies it internally.
System.out.println("bread choice is: " + todaysLoaf);
This results in output of:
bread choice is: rye
How enums are implemented
Under the covers, enum constants are static final objects declared (by the compiler) in their enum class. An enum class can have constructors, methods, and data. Enum variables are merely pointers to one of the static final enum constant objects.
You'll understand enums better if you know that the compiler treats them approximately the same way as if it had seen this source code:
class Bread extends Enum { // constructor public Bread(String name, int position) { super(name, position); } public static final Bread wholewheat = new Bread("wholewheat",0); public static final Bread ninegrain = new Bread("ninegrain", 1); public static final Bread rye = new Bread("rye", 2); public static final Bread french = new Bread("french", 3); // more stuff here }
This is an approximation because the parent class, java.lang.Enum, uses a generic parameter, and we cover generics later. Bringing generics into the definition of Enum was an unnecessary complication aimed at improving the type-safety of enums. The work could and should have been moved into the compiler. The previous code should give you a good idea of how enums are represented.
Namespaces in Java and in enumerations
Namespace isn't a term that occurs in the Java Language Specification. Instead, it's a compiler term meaning "place where a group of names are organized as a whole." Some older languages only have one global namespace that holds the names of all methods and variables. Along with each name, the compiler stores information about what type it is, and other details.
Java has many namespaces. All the members in a class form a namespace. All the variables in a method form a namespace. A package forms a namespace. Even a local block inside a method forms a namespace.
A compiler will look for an identifier in the namespace that most closely encloses it. If not found, it will look in successively wider namespaces until it finds the first occurrence of the correct identifier. You won't confuse Java if you give the same name to a method, to a data field, and to a label. It puts them in different namespaces. When the compiler is looking for a method name, it doesn't bother looking in the field namespace.
Each enumeration has its own namespace, so it is perfectly valid for enumeration values to overlap with other enums or other variables, like this:
enum Fruit { peach, orange, grapefruit, durian } enum WarmColor { peach, red, orange }
Some more Java terminology: the enumeration values apple, red, peach, plum and orange are known as enum constants. The enumeration types Fruit and WarmColor are enum types.
Enum constants make software more reliable
Here's an amazing thing: the constants that represent the enumeration values are not compiled into other classes that use the enumeration. Instead, each enum constant (like Bread.rye previously) is left as a symbolic reference that will be linked in at run-time, just like a field or method reference.
If you compile a class that uses Bread.rye, and then later add some other bread flavors at the beginning of the enumeration (pumpernickel and oatmeal), Bread.rye will now have a different numeric value. But you do not need to recompile any classes that use the enumeration. Even better, if you remove an enum constant that is actually being used by some other class (and you forget to recompile the class where it's used), the run-time library will issue an informative error message as soon as you use the now-removed name. This is a significant boost to making Java software more reliable.