Constant Objects

Earlier, I said that you can use anything to differentiate option constants. Constant objects use final instances of final classes to declare constants and differentiate them. Each constant is an object that is an instance of the constant class. The constant object class groups options together by theme. To understand how constant objects work, look at the constant object class in Example 7-8.

Example 7-8. Exception types as constant objects
package oracle.hcj.constants;
public final class NetAppException2 extends Exception {
 /** An undefined network exception. */
 public static final NetAppException2 NETWORK_UNDEFINED =
 new NetAppException2("NETWORK_UNDEFINED");
 /** A network exception caused by the server dropping you. */
 public static final NetAppException2 NETWORK_SEVER_RESET =
 new NetAppException2("NETWORK_SEVER_RESET");
 /** A network exception caused by undefined hostname. */
 public static final NetAppException2 NETWORK_INVALID_HOST_NAME =
 new NetAppException2("NETWORK_INVALID_HOST_NAME");
 /** A bad parameter exception. */
 public static final NetAppException2 LOGIC_BAD_PARAMETER =
 new NetAppException2("LOGIC_BAD_PARAMETER");
 /** An exception caused by failed authorization. */
 public static final NetAppException2 LOGIC_AUTHORIZATION_FAILED =
 new NetAppException2("LOGIC_AUTHORIZATION_FAILED");
 /** Holds the name of this constant. */
 private final String name;
 /** * Creates a new NetAppException2 object.
 *
 * @param name The name for the exception type.
 */
 private NetAppException2(final String name) {
 this.name = name;
 }
 /** * Get the name of this NetAppException2. *
 * @return The name of the NetAppException2.
 */
 public String getName( ) {
 return this.name;
 }


In this code, constants are defined not by integers but by objects. Each constant is initialized during static initialization with a call to the private constructor and passes its name to the constructor for storage in a read-only variable. This technique saves you hundreds of lines of code. To demonstrate, let's revisit the Country and Address classes. First, convert the Country class into the constant objects paradigm. The results of the conversion are shown in Example 7-9.

Example 7-9. Country as a constant object
package oracle.hcj.constants;
public class Country2 {
 public static final Country2 CANADA = new Country2("CANADA");
 public static final Country2 CROATIA = new Country2("CROATIA");
 public static final Country2 GERMANY = new Country2("GERMANY");
 public static final Country2 ITALY = new Country2("ITALY");
 public static final Country2 MEXICO = new Country2("MEXICO");
 public static final Country2 UK = new Country2("UK ");
 public static final Country2 USA = new Country2("USA");
 public static final Country2 VENEZUELA = new Country2("VENEZUELA");
 /** Holds the name of this country.*/
 private final String name;
 /** * Creates a new Country2.
 *
 * @param name The name for the exception type.
 */
 private Country2(final String name) {
 this.name = name;
 }
 /** * Get the name of this country.
 *
 * @return The name of the exception.
 */
 public String getName( ) {
 return this.name;
 }
}


Now that you have a revised Country class, it's time to revisit the problems discussed earlier. Let's start with the unchecked setter:

package oracle.hcj.constants;
public class Address2 {
 private Country2 country;
 public void setCountry(final Country2 country) {
 this.country = country;
 }
}


Since the new setter takes an object of type Country2, the user can't pass you anything except a Country2 object. Furthermore, since the constructor to Country2 is private, a user cannot create a new Country2 object. There is absolutely no need to validate the country here or anywhere else in the code! The users of the class no longer have to do any validation; the object-oriented engineering principle remains intact. If that isn't enough to sell you on the concept of constant objects, there are still more benefits. Since your countries are declared as objects and don't need to be ordered sequentially, you can declare them in any order you want. You can alphabetize them, sort them by continent, or even organize them by preferred travel destination. Also, since order doesn't matter, you can introduce new constants without doing lots of extra work. There is no need to renumber old constants to restore numbering sequencing, nor do you need to change logic throughout the code base. Simply drop in the new constant and go. Finally, as a bonus, you also get a convenient name to associate with the constant. Since you differentiate your option constants by object instance and not by integer, you never have to look up a constant based on an integer value; instead, you merely call getName( ) whenever you want to know which constant is set in the variable.

Screenshot

This can come in handy during debugging. If you've ever had to figure out what exception type number 2034 is from a stack trace, you can appreciate the benefit of seeing LOGIC_CREDIT_REFUSED instead of having to look it up.


Constant objects provide you with all of these benefits simply by the nature of their declaration. Clearly, they are superior to integral option constants.

Performance of Constant Objects

If you decide to use constant objects, it's good to know that the performance hit is near zero. In fact, constant objects consistently outperform integral option constants. When the constant objects are constructed, you incur a slight performance overhead. However, since this is done at static initialization time, the cost to you is minimal. A delay of 50 milliseconds in the setup of your program will not be noticeable. Integral constants, which are public final static constants and primitives, are substituted at compile time with their values. This may sound great, but making sure that all the classes that use the constant were recompiled is often more trouble than it's worth. Also, the checking code that constantly has to validate integral constants will not be optimized out of the code at compile time. Therefore, by using integral option constants, you save perhaps 50 milliseconds in program setup, but end up spending several minutes of CPU time executing checking code that you don't need for constant objects. However, when you compare their execution, there is virtually no difference between integral option constants and constant objects. Consider the following:

if (address.getCountry( ) == Country.US)


If Country.US is an integral option constant, the code becomes a comparison of two integers. If Country.US is a constant object, the code becomes a comparison of two memory locations. There is virtually no difference between the two. Given the benefits of constant objects, they are far better than traditional integral option constants. Constant objects can be considered to be cheap insurance, which is a term for a small task that prevents a large number of things from going wrong. Savvy project managers go for whatever cheap insurance they can find.

Indexing Constant Objects

Constant objects give you another advantage. Consider the two XML snippets in Example 7-10.

Example 7-10. An XML-encoded address
<!-- With Integral constants -->
<address >
 <street value="21 Main Street"/>
 <postal_code value="56756"/>
 <city value="Gainsville, FL"/>
 <country id="1"/>
</address>
<!-- With named constants -->
<address >
 <street value="21 Main Street"/>
 <postal_code value="56756"/>
 <city value="Gainsville, FL"/>
 <country id="USA"/>
</address>


Obviously, the second version of the XML file is much easier to read and understand than the first. With the named constant, you know the meaning of the country field of the address. When you consider the first example, it's easy to be confused about what country 1 represents. To find out, you would have to look it up in the code, most likely with integral constants. This would be clumsy and ineffective, especially if there were several of these constants. In an XML file, the named constant objects win again. The XML file in Example 7-10 also introduces a desired feature in your app. With the constant objects in the example, it would be easy to write out the constant as shown in the XML file, but what about reading the constant? It would be nice if there was a method that could return the constant object if you give it a name. Fortunately, you already have most of the tools you need to accomplish this. Since all of the constant objects are created by the same constructor, you can simply use the constructor as an indexing mechanism. This mechanism can then store all of the constants in a lookup table. Example 7-11 shows how the indexing mechanism works.

Example 7-11. An indexed constant object
package oracle.hcj.constants;
public class Country3 {
 /** Holds the Index of the country objects. */
 private static final Map INDEX = new HashMap( );
 public static final Country3 CANADA = new Country3("CANADA");
 public static final Country3 CROATIA = new Country3("CROATIA");
 public static final Country3 GERMANY = new Country3("GERMANY");
 public static final Country3 ITALY = new Country3("ITALY");
 public static final Country3 MEXICO = new Country3("MEXICO");
 public static final Country3 UK = new Country3("UK");
 public static final Country3 USA = new Country3("USA");
 public static final Country3 VENEZUELA = new Country3("VENEZUELA");
 /** Holds the name of this country. */
 private final String name;
 private Country3(final String name) {
 this.name = name;
 INDEX.put(name, this);
 }
 public String getName( ) {
 return this.name;
 }
 /** * Looks up a String to find the associated Country3 object.
 *
 * @param name The name to look up.
 *
 * @return The object or null if it does not exist.
 */
 public static Country3 lookup(final String name) {
 return (Country3)INDEX.get(name);
 }
}


When the user calls lookup( ) with a valid country name, the actual constant object used to represent the country is returned. This will allow you to use the constant class with a SAX content handler, a DOM parser, or any other mechanism for reading XML or plain text. Once you have a string, you simply input it into the lookup( ) method to get back the correct object.

Screenshot

Remember that since static initializers work in order of declaration, you will have to declare the INDEX before declaring the constant objects. If you change the order, you will get a NullPointerException when the first constant is constructed. Refer to for more information on static initializers.


Also, since the constant objects are created at static initialization, you can be sure that they are all created and loaded into the virtual machine whenever one of them is used. It is a chain reaction. The first time the program uses the constant object, the virtual machine looks for that class. If it hasn't been loaded, then the virtual machine loads the class and runs the static initialization. This causes all of the constants to be created and indexed.

Serialized Constant Objects

Your country objects wouldn't be very useful if they couldn't be written or read on different virtual machines or in different sessions. The natural solution to this problem is to change the constant object to be serializable, as shown here:

package oracle.hcj.constants;
public class Country4 implements Serializable {
 // . . . same as Country3
}


Screenshot

Serialization is a large and involved topic, which is thoroughly explained in Java I/O by Elliotte Rusty Harold (Oracle). I encourage you to check out this tutorial if you are unfamiliar with the mechanisms behind serialization.


Now that your constant object is serializable, you can write it to disk and read it back. Furthermore, you can use it in an EJB or RMI app, which require passed data to be Serializable. Everything seems to be perfect, but there is one problem: simply marking the object as Serializable is not enough. The Serializable constant object can result in two instances of Country4.GERMANY in memory. You may think that since the constant object is final and static, this will guarantee that only one instance of each constant object will be in memory. I used to think the same thing until I was blindsided by a vicious bug in a genetic research system. I was debugging a particular piece of code that was failing a rule check, even though the data input should have passed the rule check. The particular data I was working with came from an EJB over serialization. It never occurred to me that there could be two instances of the same constant object in memory at the same time. I will show you how I solved the problem with something as mundane as file I/O:

// From oracle.hcj.constants.SerialTest public class SerialTest {
 public static void writeCountry4( ) throws IOException {
 FileOutputStream fos = new FileOutputStream(getFilename( ));
 ObjectOutputStream oos = new ObjectOutputStream(fos);
 oos.writeObject(Country4.GERMANY);
 }
}


This snippet is the write portion of the test. It is fairly mundane serialization to a file. The target country of GERMANY is simply stored to a file. After you store the country, you immediately read it back from the file:

// From oracle.hcj.constants.SerialTest public class SerialTest {
 public static void readCountry4( )
 throws FileNotFoundException, IOException, ClassNotFoundException {
 // --
 System.out.println("Country4 in VM"); System.out.println("Type = " + Country4.GERMANY.getClass( )); System.out.println("Name = " + Country4.GERMANY.getName( )); System.out.println("Hashcode = " + Country4.GERMANY.hashCode( ));
 // -- FileInputStream fis = new FileInputStream(getFilename( ));
 ObjectInputStream ois = new ObjectInputStream(fis);
 Country4 inCountry = (Country4)ois.readObject( );
 // --
 System.out.println("----------------");
 System.out.println("Country4 read in"); System.out.println("Type = " + inCountry.getClass( )); System.out.println("Name = " + inCountry.getName( )); System.out.println("Hashcode = " + inCountry.hashCode( ));
 // -- System.out.println("----------------");
 System.out.println("Identical = " + (inCountry == Country4.GERMANY));
 }
}


The first thing this method does is write out the details of the Country4.GERMANY object to the console. It then opens the file you created in the write portion of the program, reads the Country4 object, and writes out its details. Finally, it compares the two objects. The output is shown here:

>ant -Dexample=oracle.hcj.constants.SerialTest run_example run_example:
 [java] Country4 in VM
 [java] Type = class oracle.hcj.constants.Country4
 [java] Name = GERMANY
 [java] Hashcode = 17870931
 [java] ----------------
 [java] Country4 read in
 [java] Type = class oracle.hcj.constants.Country4
 [java] Name = GERMANY
 [java] Hashcode = 4660784
 [java] ----------------
 [java] Identical = false


After the read phase is run, there are two Country4.GERMANY objects in the virtual machine. The same results would be obtained regardless of which constant object you used. Both versions of the object are the same. The problem is that the hash code, which is based on their memory location (unless you override that method), is different. In the sample run, the virtual machine first ran the static initializer for Country4 and then initialized the version for itself. However, when the object was read in from the file, the virtual machine placed this object in its own memory location. There are two instances in memory; since your rule checked for equals based on identity, your rule check failed. The correct resolution to the problem is to fix the serialization mechanism used in the Country4 object. To fix the serialization, you have to override a "magic" method that will return a different object if the user reads a Country4 object. The new variation, called Country5, shows how this works:

package oracle.hcj.constants;
public class Country5 implements Serializable {
 // . . . same as Country3 and Country4
  protected Object readResolve( ) throws ObjectStreamException {
 Object result = Country5.lookup(this.getName( ));
 if (result == null) {
 throw new RuntimeException(
 "Constant not found for name: " + this.getName( ));
 }
 return result;
 }
}


The readResolve( ) method is a "magic" method because it is not required by any interface or superclass; however, it is found by the serialization mechanism if it is declared. The serialization reads in the Country5 object and then looks to see whether a readResolve( ) method was defined for this class. Since it was, this method is called by the serialization mechanism. Inside your method, use the handy index to find the correct object already in the virtual machine (if it exists) and return it rather than returning a new object instance. The original object that was read in goes out of scope and is dumped on the next garbage-collection pass—problem solved. Also note that there is a writeResolve( ) method as well. This method performs the inverse of the readResolve( ) method. That is, before the object is written, this method is called, and the object you return is used in the write operation. In this case, you don't need to use this method because you need only to write out the string value of the constant, so the default serialization mechanism is sufficient. On the other hand, if you had to write out the object in a format other than the natural serialization format, you would have had to override writeResolve( ). Now that your object is resolved, you can try your test again, this time with the repaired Country5 object. The result is shown here:

>ant -Dexample=oracle.hcj.constants.SerialTest run_example run_example:
 ... old output snipped ...
 [java] Country5 in VM
 [java] Type = class oracle.hcj.constants.Country5
 [java] Name = GERMANY
 [java] Hashcode = 16399041
 [java] ----------------
 [java] Country5 read in
 [java] Type = class oracle.hcj.constants.Country5
 [java] Name = GERMANY
 [java] Hashcode = 16399041
 [java] ----------------
 [java] Identical = true


This is perfect. The objects read in and write out exactly as they should.

Screenshot

This serialization fix will work for any kind of constant object or other static object.


Accounting for Multiple Constant Object Types

Since creating constant objects is such a common task, it would be beneficial if you could abstract out some of the common details into a base class that can be used by developers who need this functionality. To accomplish this, you need to create a class named ConstantObject in your oracle.hcj.constants package.

Screenshot

The ConstantObject class is a general-purpose class that would normally reside in a utility library separate from app-specific code. However, for the sake of this tutorial, I don't want to complicate things, so I keep it in the same package. Upon publication of this tutorial, many of its general-purpose tools will be submitted to the Jakarta commons libraries, available at http://jakarta.apache.org/.


The ConstantObject class has some interesting aspects of its own. Since the class will provide a lookup mechanism for members of all descendants, you need to slightly alter your storage mechanism. Consequently, the constructor gets a little more complicated, as shown in Example 7-12.

Example 7-12. A constructor for a constant object utility
package oracle.hcj.constants;
public abstract class ConstantObject implements Serializable {
 private static final Map CONSTANTS_MASTER_INDEX = new HashMap( );
 /** Holds value of property name. */
 private final String name;
 protected ConstantObject(final String name) {
 if (name == null) {
 throw new NullPointerException("The name may not be null.");
 }
 Map constMap = (Map)CONSTANTS_MASTER_INDEX.get(this.getClass( ));
 if (constMap == null) {
 constMap = new HashMap( );
 CONSTANTS_MASTER_INDEX.put(this.getClass( ), constMap);
 }
 if (constMap.containsKey(name)) {
 throw new IllegalArgumentException(ERR_DUP_NAME);
 }
 this.name = name;
 constMap.put(name, this);
 }
 public final String getName( ) {
 return this.name;
 }
}


To make things easier on the derived classes, the read-only name property and the indexing are in the base class. However, unlike the constructor in the Country5 class, this constructor must index multiple classes worth of constant objects. To accomplish this, create a map of maps, the outer map keyed by class and the inner map keyed by constant name. Then simply store the constant in the correct map for its class. Since the class is indexed when it is initialized (which happens only once per run in your program) the overhead in indexing the constants is minimal. Your ConstantObject constructor is protected to prevent the user from instantiating the objects. It also has a bonus cheap insurance feature. It checks the name of the object you are creating and makes sure that the name isn't already being used. So, if you are in a rush and forget to change the name, this code will catch the problem.

Screenshot

While writing this tutorial, the cheap insurance that blocks duplicate names actually caught a mistake I made while working on the sample code.


Now all you have to do is provide a way to look up the objects. The method that does the lookup is shown here:

package .utilities;
public abstract class ConstantObject {
 public static final ConstantObject lookup(final Class dataType, final String name) {
 return (ConstantObject)
 ((Map)CONSTANTS_MASTER_INDEX.get(dataType)).get(name);
 }
 public static final Map lookup(final Class dataType) {
 return Collections.unmodifiableMap(
 (Map)CONSTANTS_MASTER_INDEX.get(dataType));
 }
}


Looking up the constant is merely a matter of finding the correct class map for the given data type and then the correct key. Also, a utility was added that allows you to get a map of all of the constants for a particular constant class. This map is returned as an unmodifiable map to prevent any accidental changes. Finally, you need to alter your serialization to account for the problem shown in the last section. The following code shows the changed method:

package oracle.hcj.constants;
public abstract class ConstantObject {
 protected Object readResolve( ) throws ObjectStreamException {
 Object result = lookup(getClass( ),getName( ));
 if (result == null) {
 throw new RuntimeException(
 "Constant not found for name: " + getName( ));
 }
 return result;
 }
}


Once you have the correct data type, obtained with getClass( ), simply employ the lookup mechanism to get the correct constant and then return that constant. This will work flawlessly for any class that the virtual machine can access. Using the ConstantObject class is extremely easy. To illustrate, let's convert the old country class to use the ConstantObject class. Example 7-13 shows the result of this conversion.

Example 7-13. Using the constant object utility
package oracle.hcj.constants;
public final class Country6 extends ConstantObject {
 public static final Country6 CANADA = new Country4("CANADA");
 public static final Country6 CROATIA = new Country4("CROATIA");
 public static final Country6 GERMANY = new Country4("GERMANY");
 public static final Country6 ITALY = new Country4("ITALY");
 public static final Country6 MEXICO = new Country4("MEXICO");
 public static final Country6 UK = new Country4("UK");
 public static final Country6 USA = new Country4("USA");
 public static final Country6 VENEZUELA = new Country4("VENEZUELA");
 private Country6(final String name) {
 super(name);
 }
}


It's that easy! The revised Country6 class simply calls its superclass constructor, which does all of the difficult work. You can use this mechanism to create any kind of constant object you want.

      
Comments