digitalmars.D - mostly OT: Java adds enums
- Mark T (437/437) Dec 12 2004 After 8 years or so Java finally adds enums, I guess there is still hope...
- =?ISO-8859-1?Q?Anders_F_Bj=F6rklund?= (13/16) Dec 12 2004 eeew, they just hacked it in using a Class...
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
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