You give up a lot of options in moving from C++ to Java, but you don't have to give up as much as you might think.
Introduction
Java's plusses are very easy to get used to. For example, I never find myself saying "Darn it, I want to manage the heap myself." Java's minuses are another story. One feature of C/C++ I have often missed in Java is the enum.
The lack of enum first bit me about a year ago, when I was just starting to learn Java. I was typing in a program from a book, which was supposed to display a button on the screen. The program compiled fine, but displayed nothing. There were no error messages.
A character by character examination of what I had typed revealed the problem line:
add("Nort", new Button("Red"));Instead of typing in the word "North" I had typed "Nort". The add method of the Component class (part of Sun's abstract windowing library) was expecting one of five strings as its first argument: "North", "South", "East", "West", or "Center". If the String argument passed wasn't any of these, the version of the AWT (Abstract Windowing Toolkit) I was using failed silently the button was not added and the add method reported no errors.
In C/C++, the add method wouldn't have been defined this way. Instead an enum would have been defined, and the add method would have been expecting a variable of this enum type as its first argument:
// C++ Declaration of the enum and of the add // to which enum argument is passed enum Position {North, South, East, West, Center}; void add (Position pos, Button b);Calling this C++ version of add with "North" misspelled "Nort" would cause the C++ compiler to issue an error message.
Java programmers have developed constructs that provide just as much type safety as enum. These constructs (typically classes) can be written to protect careless typists such as myself. In this article I will review some of the most effective replacements I've found for enum in Java.
Public Static Constants as enums
The C++ enum provides a way of defining a set of integer constants. One of the simplest ways to simulate enum in Java is to just define the constants directly. The constants should be static, since they are associated with the entire class and not a particular object. They should be made public so users of the class can access them.
The following Book class contains static constants to classify the age level of the book's target reader:
public class Book { public Book(String titleArg, int ageLevelArg) { title = titleArg; ageLevel = ageLevelArg; } // final indicates variables constant public final static int TODDLER = 1; public final static int CHILD = 2; public final static int TEENAGER = 3; public final static int ADULT = 4; private int ageLevel; private String title; }Callers wishing to pass age levels to the book can use the constants, and if they make a spelling error the compiler will detect it.
Sometimes a class will contain more than one set of constants. Grouping related constants together in their own class makes the interface clearer. Also, you need not define the constants as integers; for example, you can define them as String constants.
There is a slight problem with using integer or String constants in place of the missing enum: callers aren't forced to use the constants! If the constants are integers, for example, the caller can pass any raw integer, and the compiler won't detect that the argument is incorrect. However such an error becomes apparent whether you catch it in your code, or it crashes your program it will show up at run time, not at compile time. Furthermore, using integer constants encourages bad programming style. Some programmers are addicted to using numbers in their code. I've seen such programmers print out a class containing static constants so they could know which number to use. Their code runs correctly, but is difficult for anyone else to understand.
In the following section I present a method to enforce the use of static constants.
Defining a New Constant Type
By making the constants instances of a new type you can enforce their use. Consider the following redefinition of the Book classes.
class BookAgeLevel { private BookAgeLevel(){} // Following are all the BookAgeLevel objects // there will ever be. final static BookAgeLevel TODDLER = new BookAgeLevel(); final static BookAgeLevel CHILD = new BookAgeLevel(); final static BookAgeLevel TEENAGER = new BookAgeLevel(); final static BookAgeLevel ADULT = new BookAgeLevel(); } class Book { Book(String titleArg, BookAgeLevel ageLevelArg) { title = titleArg; ageLevel = ageLevelArg; } public BookAgeLevel getAgeLevel() { return ageLevel; } private BookAgeLevel ageLevel; private String title; }The constants defined within BookAgeLevel are BookAgeLevel objects. Because BookAgeLevel has a private constructor, no code outside the BookAgeLevel class can instantiate a BookAgeLevel object. Since the constructor of the Book class expects a BookAgeLevel argument, one of these constants must be passed to it. Of course the constant might be passed indirectly, as in the following:
BookAgeLevel level = BookAgeLevel.TEENAGER; Book book = new Book ("Mystery of Java",level);Routines that need to do more than simply store away their arguments often have a format similar to the following:
void printBookLevel (Book b) { BookAgeLevel l = b.getAgeLevel(); if (l == BookAgeLevel.TODDLER) System.out.println("Toddler"); else if (l == BookAgeLevel.CHILD) System.out.println("Child"); else if (l == BookAgeLevel.TEENAGER) System.out.println("Teenager"); else if (l == BookAgeLevel.ADULT) System.out.println("Adult"); else if (l == null) System.out.println("null argument."); else System.out.println("unhandled case"); }Since printBookLevel is passed an object instead of a basic type, the next-to-last else-if clause is needed to check for a null. In Java, an instance of a class is really a reference to an object rather than an object itself, and that reference may have never been initialized.
The above routine prints out the appropriate message for the age level of the book passed in as an argument. If the last clause is reached (the one that prints out "unhandled case"), there must be an error in the printBookLevel routine itself. printBookLevel should check for every BookAgeLevel that was defined in the BookAgeLevel class if this routine encounters an unknown BookAgeLevel, it might mean that someone modified the class without providing a corresponding update to the routine.
The possibility of this error occurring suggests that the design of the Book class might be faulty. In the next section, I present a technique that eliminates this sort of error.
Simulating enum with Inheritance
When I find myself writing routines structured like printAgeLevel, I become a little suspicious about my approach to the problem. The if-else clauses seem to be doing a test to determine the type of BookAgeLevel. Instead, it might make sense to create different BookAgeLevel subtypes:
abstract class BookAgeLevel { public void print() { System.out.println(string); } // subtype constructors will set. String string; } class Toddler extends BookAgeLevel { Toddler(){super.string = "Toddler";} } class Child extends BookAgeLevel { Child(){super.string = "Child";} } class Teenager extends BookAgeLevel { Teenager(){super.string = "Teenager";} } class Adult extends BookAgeLevel { Adult(){super.string = "Adult";} } class Book { Book (String titleArg, BookAgeLevel ageLevelArg) { title = titleArg; ageLevel = ageLevelArg; } BookAgeLevel getAgeLevel() {return ageLevel;} private String title; private BookAgeLevel ageLevel; }Using inheritance is often preferable to using enum, even in C++. Once the subtypes are defined, creating a new book and printing its age is simple:
Book b = new Book( "Kindergarten Days" new Toddler()); b.getAgeLevel().print();Conclusion
When I find myself missing enum, I first think about whether my program should be restructured to use inheritance to make the missing font irrelevant. If I decide that I really should be using an enum equivalent, I define a new argument type, so that callers of my class's methods will be forced to pass one of the static constants I've defined as an argument. This is a little bit more work than just defining int or String constants. I think the extra work is worthwhile as much as possible I like to protect the users of my classes from making silly mistakes. I hope the writers of the classes I use will do as much for me.
Further Reading
Tom Cargill. C++ Programming Style (Addison-Wesley, 1992).
Bruce Eckel. Thinking in Java (Prentice Hall, 1998).
Michael Bridges received a bachelor's degree in Electrical Engineering and Economics from Carnegie Mellon University and a master's degree in Computer Science from the University of Pennsylvania. He has done many different kinds of programming, including embedded systems. Currently he is at Destiny Software (www.destinyusa.com), where he gets to use his two favorite languages, C++ and Java, to implement online financial systems.