www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - mostly OT: Java adds enums

reply Mark T <Mark_member pathlink.com> writes:
After 8 years or so Java finally adds enums, I guess there is still hope for D
adding a true boolean type in the future.
===========================================
from JDJ magazine:
Exploring Enums: The Wait Is Finally Over
November 30, 2004
Summary
To enumerate means to itemize or to list. In the world of programming,
enumerations, enums for short, are used to represent a finite set of 
values (constants) that a variable can attain. In other words, it defines the
domain of a type. For instance, different states of a fan switch - off,
low, medium, and high - make up an enumeration.

By Ajith Kallambella

To enumerate means to itemize or to list. In the world of programming,
enumerations, enums for short, are used to represent a finite set of 
values (constants) that a variable can attain. In other words, it defines the
domain of a type. For instance, different states of a fan switch - off, low,
medium, and high - make up an enumeration.

Since the first release of Java, programmers have been complaining 
about the lack of core language support for enumerated types. After all, Java
improvised on the shortcomings of C++ and touted type safety, so it was
only natural for the developer community to expect support for a true
enum-type. During what seemed like an eternally long wait, many ad-hoc 
enum representations evolved and most of them shared a common premise - 
modeling enumerations based on a primitive type, usually an int; see Listing 1 
for an example.

Although practical, such implementations have long been frowned upon by
Java purists as a hack that can best be described as brittle. What 
exactly is wrong with such an implementation? In essence, the main disadvantage 
of faking enumerated types using primitives is the lack of strong typing 
and hence the inability to catch errors at compile time. Other shortcomings 
are less readable code, deviations from object-oriented concepts such as
encapsulation, the absence of a namespace requiring an explicit prefix 
for all references, and the dangers of exposing the internal implementation 
to client code.

Let's look at some of these drawbacks in more detail.

Loose type checking is the result of a compiler treating the faked
enumeration just like any other primitive variable. Since the compiler
doesn't know anything about the cohesiveness of the enumeration of 
constant
values, it doesn't catch, for instance, a definitive assignment of an 
out
of range value. In other words, lack of an explicit type totally gives 
away
the benefits of compile-time error detection.

Such ad hoc approaches violate some of the basic object-oriented
principles. In the previous example, the states of the switch lack
encapsulation. Attributes representing the constants and operations are 
not
grouped together but are scattered within the enclosing scope. Since 
the
client code is well aware of implementation details, even a small 
change
such as renumbering them might result in broken client code. Also note 
the
lack of explicit scoping. Since enum attributes are listed along with 
other
class attributes, the enclosing scope is often that of the declaring 
class
or interface. This makes their grouping vague, unreadable, and 
sometimes
error prone.

In his book Effective Java, Joshua Bloch presents a pattern for 
type-safe
enums that offers both compile-time safety and better encapsulation.
java.awt.Color uses a similar strategy to hide the int-enum 
implementation
under the sheet. However, after reading through pages full of Java 
code,
you begin to wonder if the juice is worth the squeeze. It only 
reinforces
the need for core language support for enum types. For a language that
touts type safety, the idea of faking enums with such elaborate
pyrotechnics seems rather odd.

Enter the New Enum
The wait is finally over and true enums are here. Enums are a type of 
their
own in J2SE 5.0. Among many other developer-friendly features 
recommended
in JSR 176, Tiger (the code name for the new release) packs the 
powerful
punch of type-safe enums. Listing 2 shows how we rewrite Fan.java using
true enums. (Listings 2-9 can be downloaded from
www.sys-con.com/java/sourcec.cfm.)

Note the new enum keyword introduced to support the new type and also 
how
enumerated constants are listed - they are neither strings nor ints but
belong to their own declared type, i.e., SwitchState. Because it's a 
true
type, all the benefits of strong typing automatically kick in. For
instance, the following code attempting to set an invalid state



new Fan().setState(6);

is automatically detected at compile time and reported an error,
eliminating the need for several lines of validation code:



setState(Fan.SwitchState) in Fan cannot be applied to (int)

Let's look at other useful features provided by the enum type.

Support for Namespace
In the previous example, all the listed constants, e.g., states of the 
fan
switch, implicitly belong to the enclosing type, appropriately named
SwitchState, requiring an explicit namespace qualification to all
references to states. For example:



SwitchState.Low

Namespace adornment also helps avoid collisions. If we were to declare
another enum type for class Fan using a similar set of constants, say:



public enum DurabilityRating { Low, Medium, High }

it will result in no name collision. SwitchState.Low can be 
distinguished
from DurabilityRating.Low because of their enclosing scope.

Since enum SwitchState is declared as public, and with the type system
providing a namespace for each enum constant, it can be accessed 
outside
the scope of class Fan just like any normal attribute reference, e.g.,
Fan.SwitchState.Medium.

Auto Conversion to String Values
The new enum type easily facilitates descriptive printing using an 
internal
implementation of the toString() method. The default implementation 
returns
a string representation of the constant as declared, and, if you don't 
like
it, it can be changed by overriding toString(). It's that simple.

Works with Programming Constructs
Since enums are their own types, some of the standard Java constructs,
notably the switch and for statements, have been enhanced to work with 
enum
types. Listing 3 provides an example of the switch construct.

Switch statements are useful for simulating the addition of a method to 
an
enum type from outside the type. This can be very useful if for some 
reason
the enum definition cannot be modified, but needs to be extended.

There is something rather interesting about this switch statement - 
note
how the enum constants in case statements appear bare, e.g., 
unqualified
with their namespace. J2SE 5.0 makes life easier by attempting to 
resolve
identifiers in the immediate context. This is somewhat similar to the 
new
static imports feature. In this example, however, you are required to 
omit
the namespace. Try including it and you'll get an error message:



Fan.java:16: an enum switch case label must be the unqualified name of 
an
enumeration constant
case SwitchState.Off : return SwitchState.Low ;

Listing 4 provides an example that uses a for-loop construct to iterate
through enums.

The values() method returns an array that contains enum constants for 
this
enum in the order in which they are declared. By the way, the for-loop
shown here is called "enhanced for-loop", another neat feature in the 
new
release. They are also called as "for-in loop" since you read them as
"for-state-in-Switchstate.values". This new construct simplifies 
iterating
over a collection of values - both arrays and Java collections - by 
taking
away the need to inspect the size, use a temporary index variable, and 
cast
each element to the appropriate type.

Looking Under the Hood
In the spirit of empiricism, let's look under the hood and see how 
enums
are implemented. Compiling Fan.java (for the complete source code see 
the
resources section) generates two .classes: Fan.class and
Fan$SwitchState.class. The latter contains our enum definition.



D:\MyJava\JDJ\src>javap  Fan$SwitchState

Compiled from "Fan.java"
public final class Fan$SwitchState extends java.lang.Enum{
public static final Fan$SwitchState Off;
public static final Fan$SwitchState Low;
public static final Fan$SwitchState Medium;
public static final Fan$SwitchState High;
public static final Fan$SwitchState[] values();
public static Fan$SwitchState valueOf(java.lang.String);
static {};
}

As you can see, the process of compilation results in the automatic
generation of a new class that extends java.lang.Enum. In other words, 
the
enum keyword acts as a shorthand representation for the autogenerated
class. This is what the authors of JSR 201 meant by "linguistic support 
for
type-safe enumeration pattern." It's worth mentioning that every new
language extension introduced in Java 5.0 is implemented by modifying 
the
source-to-byte code compiler so the JVM implementation remains 
unchanged.

If you notice the naming convention adopted for generated enum types,
you'll recognize that they closely resemble the inner class syntax, 
e.g.,
<enclosingType>$<thisType> format. All enum classes are final 
subclasses of
java.lang.Enum, serializable, and comparable. They come with predefined
toString, hashCode, and equals methods. All methods except toString are
final.

Note that all enlisted enum constants are represented as final 
self-type
variables of the class. This is done to ensure no instances exist other
than those in the generated class. In other words, enums are Singletons
and, for the same reason, they can't be instantiated using new or 
cloned.
The clone method in java.lang.Enum throws a CloneNotSupportedException.

Since enums are compiled into their own class files, their definition 
can
be changed, e.g., enum constants can be added, deleted, and reordered
without having to recompile the clients. If you removed an enum 
constant
that a client is using, you'll fail fast with an error message.

Now let's look at some advanced features.

Flavors of Declaration
Enums can be declared in various flavors. The enum FanState above is an
example of an inline declaration. These are useful for defining enums 
that
have a limited scope - the use and throw type of enums. Since Java 5.0
introduces the keyword enum at the same level as class and interface
keywords, enums can be declared just as you would a new class or an
interface - in its own .java file (see Listing 5).

Normal rules of access visibility that apply to standalone Java classes
also apply to enums, whether they are declared inline or in a separate
file. For example, if you omitted the public keyword, the enum type 
will
have the default package visibility and hence be accessible only within 
the
package. Similarly if the SwitchState enum were to have a private 
access
specifier, it wouldn't be accessible outside the class scope of Fan. 
You
get the point.

As with other new features introduced in Java 5.0 such as generics, 
enums
have been used in several JDK packages. JDK 5.0 includes several enum 
types
to support core classes. For instance, the newly introduced
java.lang.Thread class uses an enum named State to represent the 
current
state of the thread. Some of the best enum candidates haven't been
converted over yet - like the most quoted Color class. You can use 
Javadoc
as your field guide for spotting enums in Tigerland. The new API 
Javadoc
conveniently lists all enums in the package-summary.html along with 
other
top-level types. Once you have spotted them, go ahead and open the 
source
code and see how the preachers practice. It's always fun.

Class-like Behavior
Since enums are class-like critters, they support most, if not all (see 
the
Caveat Emptor sidebar), semantics supported by normal Java classes. The
enum type definition can have one or more parameterized constructors,
implement interfaces, and support class body elements such as 
attributes,
methods, instance and static initializer blocks, and even inner 
classes.

In addition, arbitrary fields and methods can be added to individual 
enum
constants. Such constant-specific class bodies define anonymous classes
inside enum classes that extend the enclosing enum class. Listings 6-9
illustrate the use of some of these features.

There's a lot to digest here, so let's tackle them one at a time.

We are defining three types of accounts as enums - checking, savings, 
and
investment. First things first - we need some clarification of 
terminology
here. Every enum constant has a declaring class, which is also called 
its
type. The term "enum type" is used to refer to the declaring class, 
e.g.,
AccountType. When we use the term "enum constant", it's referring to 
all
enlisted contents contained in the scope of AccountType, e.g., 
Checking,
Savings, and Investment. It shouldn't be very confusing. Just think of 
any
class declaration and its objects - the enum type is analogous to the 
class
declaration and enum constants, the objects.

Enums Are Classes
Similarities between a normal class declaration and an enum declaration 
are
hard to miss. The enum type AccountType implements the IAccountType
interface and has two constructors. What does it mean? To implement an
interface, the enum type must implement every method defined in the
interface. Since all enum constants are objects of the declared type, 
they
share the common implementation of methods getAvgBalanceMethod and
getInterest- Rate. An enum constant declaration, when followed by
arguments, invokes the constructor defined by the enum type. In our
example, the enum constant Savings invokes the constructor



AccountType(double interestRate)

with argument 3.5.

All the standard rules of constructor overloading and selection 
specified
by the Java Language Specification are followed here. Since enum 
constant
Checking has no arguments following its declaration, it's necessary to
provide a no-arg default constructor. If the enum class has no 
constructor
declarations, a parameterless default constructor is automatically 
provided
to match the implicit empty argument list.

Notice how an enum type can declare methods and attributes, again, just
like a normal Java class. For member type declaration, all rules of
scoping, access specifiers, and visibility are valid and therefore must 
be
followed with regard to instance variables and methods. There is 
something
rather interesting with the type AvgBalanceMethod. It's an enum within 
an
enum. Since an enum type can support all types of class members, they 
can
support enums too. Since AvgBalanceMethod is declared as public, it's
visible outside the scope of the enclosing AccountType. Notice how it's
accessed in the Account class.

Constant Class Bodies
Enum constants can be associated with an arbitrary class body often
referred to as a constant class body. Such classes are very similar to
anonymous class declarations (even named similarly) and are implicitly
static. This means they can access only static defined in the enclosing
scope. Since constant classes are implicit extensions of the enclosing
class, they can override methods defined in the enum type. In our 
example,
enum constant Investment overrides getAvgBalanceMethod defined by
AccountType. In fact, it would be perfectly legal to declare 
AccountType as
abstract and force every enlisted enum constant to implement methods in 
the
interface in their constant class body. A word of caution: although 
support
for constant class bodies is a very powerful feature, it may be wise to
avoid stretching them because of the same reasons why excessive use of
anonymous classes is discouraged - they are less readable and hard to
debug.

It's important to mention here that only a nonfinal instance method in
java.lang.Enum is the toString() method. You can override and implement 
the
per-constant toString() method to return a descriptive name for each 
enum
constant. The default implementation of toString() returns just the 
string
equivalent of the constant. Therefore, as illustrated in the example, 
it
may be a good idea to override when a more descriptive literal is
necessary.

Before we proclaim victory, two new classes introduced for enum support
deserve consideration: java.util.EnumMap and java.util.EnumSet. As 
their
names suggest, they are enum-enabled counterparts of the standard Java
collection implementation. They both require that each element 
maintained
by the collection belong to one enum type. In short, they won't let you 
mix
and match enum constants from different types. It's rather interesting 
that
they both retain the elements, e.g., the enum constants, in their 
declared
order, totally ignoring any custom implementation of the compareTo()
method. Be sure to check out their API documentation.

Conclusion
The new enums are a robust way to implement constant named lists and 
make
them a compelling alternative to ad hoc enum implementations. With
class-like behavior and the ability to support arbitrary class members,
they are certainly bigger than they appear. Although you may not have 
an
immediate need to use them in your projects, or at least not every 
feature
they offer, keep them in your toolbox and you'll soon find them handy.

Resources


Java 5.0 Release candidate home page: http://java.sun.com/j2se/1.5.0
/index.jsp
JSR 201 - check out the enum draft spec:
http://jcp.org/en/jsr/detail?id=201
JSR 176 - J2SE 5.0 Release contents: 
http://jcp.org/en/jsr/detail?id=176
SIDEBAR

CAVEAT EMPTOR
Let the buyer beware: a simple axiom often used in commerce summarizes 
it
well - it means the buyer alone is responsible for assessing the 
quality of
a purchase before buying. Enums are so powerful, once you learn the 
ropes
it's easy to be tempted to stretch and do "cool" things. Before you 
know
it, bad things start happening.

Always check out the API spec and know the expected behavior before you 
put
something to use. As a smart programmer, you also need to know the
limitations of the enums so that it can save your bacon some day.

1.  You can't new enums: Remember they are singletons. For the JVM to
handle them properly, only one instance of each should exist. They are
automatically created for you. Luckily the compiler catches explicit
instantiation. For the same reason, enum constructors are implicitly
private. Think about it.

2.  You can't extend enums: One enum cannot extend another one. You 
can't
extend the primordial java.lang.Enum either. That's how it works and if 
you
are worried about this limitation, your design demands a second look. 
You
shouldn't need a hierarchy of enums.

3.  You can't declare enums locally: Enum types cannot exist in any 
scope
lower than a class scope. You can't define them within a method.

4.  Order matters: Within the enum type class body, the constants must
appear before other class elements such as attributes, methods, and
constructors. This is true at least as of the 5.0 beta release.

5.  No nulls please: An enum constant cannot be null. It's that simple.

6.  Don't use ordinal: Code that uses an ordinal() method, e.g., logic
based on the position of an enum constant as it appears in the 
declaration,
must be discouraged. Your code will break at runtime if constants are
subsequently reordered. What's more, this is a runtime error. The 
compiler
will not catch such a thing.

7.  Everything that sounds the same, isn't: There are a few other 
classes
in JDK 5.0 that sound very similar - Enumeration, EnumControl, to name 
a
few. Don't assume they are enum implementations. Check the Javadoc.

8.  Spare the serialization: Enum serialization isn't like the normal 
one
you have seen. The process by which enum constants are serialized 
cannot be
customized. Any class-specific writeObject and writeReplace methods 
defined
by enum types are ignored during serialization. Similarly, any
serialPersistentFields or serialVersionUID field declarations are also
ignored - all enum types have a fixed serialVersionUID of 0L. Again, 
this
shouldn't concern you too much. Let the language take care of the
specifics.
Dec 12 2004
parent =?ISO-8859-1?Q?Anders_F_Bj=F6rklund?= <afb algonet.se> writes:
Mark T wrote:

 After 8 years or so Java finally adds enums,
eeew, they just hacked it in using a Class... Then again I guess that was always the proposed solution to the lack of enums, so it makes sense And if strings and arrays are Objects, then why shouldn't enums be too ? (instead of primitives)
 I guess there is still hope for D
 adding a true boolean type in the future.
I wouldn't bet on it. Walter likes his arithmetic logic. A boolean type does not make much sense until the conditional and relational statements are changed ? But it would be great if bool was made a proper keyword. --anders PS. There is still plenty of hope of D adding a non-zero bit type in the present... ;-)
Dec 12 2004