Many Java developers, including myself, like using the typesafe constant idiom because it provides cleaner code by removing the need to validate whether a constant value is valid or in range. I don't detail too much here, as the idiom is well documented both in JavaWorld and in several books (see Resources below).
This idiom's canonical form, shown below, uses the ==
operator to compare values:
public final class NumberConstants{ public static final NumberConstants ONE=new NumberConstants(); public static final NumberConstants TWO=new NumberConstants(); public static final NumberConstants THREE=new NumberConstants(); private NumberConstants(){} } void test(NumberConstant num){ //No need to check that the value is on range if(num==NumberConstant.ONE){ //take some action } }
Using the idiom this way works well until you need to make the class serializable. Vladimir Roubtsov discusses the problem with serialization and offers an elegant solution in "Java Tip 122: Beware of Java Typesafe Enumerations."
Roubtsov's solution uses the readResolve()
method to return the correct object reference for the constant that matches the local version. That means you can continue using the convenient ==
operator even after one of the constants has been deserialized. This approach's only downside is that you must dirty your hands and implement readResolve()
every time you need to write a new class of constants—or do you?
I'm a big fan of reusable code and created an alternate, simple solution. My approach handles the serialization problems for simple persistence and works in many distributed systems without the need to implement readResolve()
in every new class of constants. The solution presented here uses an abstract class you extend as follows:
public final class NumberConstants extends AbstractConstant{ public static final NumberConstants ONE=new NumberConstants(); //etc private NumberConstants(){} }
The AbstractConstant
class is declared (as its name suggests) abstract
so it cannot be instantiated. It also implements Serializable
.
The class declares two private methods writeObject()
and readObject()
. As usual, if these two methods are present in a Serializable
class, then they will automatically invoke when the object is serialized/deserialized.
The AbstractConstant
class identifies which field of the subclass is being serialized and then writes that field's name into the stream to guarantee uniqueness—since you can't have duplicate field names in a Java class. However, duplicate field names are possible within a class hierarchy; so, for this technique to work, subclasses of the AbstractConstant
class must be declared final
—a rule that should be applied to all typesafe constants. Here's the code for the AbstractConstant
class starting with the class definition and the writeObject()
method, which writes the field name of the constant being serialized into the ObjectOutputStream
:
import java.lang.reflect.Field; public abstract class AbstractConstant implements java.io.Serializable{ private transient String _fieldName; private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { Class clazz=getClass(); Field [] f=clazz.getDeclaredFields(); for(int i=0;I<f.length;i++){ try{ int mod=f[i].getModifiers(); if(Modifier.isStatic(mod) && Modifier.isFinal(mod) && Modifier.isPublic(mod)){ if(this==f[i].get(null)){ String fName=f[i].getName(); out.writeObject(fName); } } }catch(IllegalAccessException ex){ throw new java.io.IOException(ex.getMessage()); } } }
The readObject()
method then reads back the field name from the stream and assigns it to _fieldName
, which is later used in the readResolve()
method. The readResolve()
method invokes after readObject()
when an object is deserialized:
private void readObject(java.io.ObjectInputStream in) throws java.io.IOException{ try{ _fieldName=(String)in.readObject(); }catch(ClassNotFoundException ex){ throw new java.io.IOException(ex.getMessage()); } }
The readResolve()
method then identifies the object's local static instance (by matching on the _fieldName
field) and returns the corresponding object. The net result is that you can still use the ==
operator after deserialization because the local static constant replaces the deserialized one:
public Object readResolve() throws java.io.ObjectStreamException{ try{ Class clazz=getClass(); Field f=clazz.getField(_fieldName); return f.get(null); }catch(Exception ex){ ex.printStackTrace(); throw new java.io.InvalidObjectException( "Failed to resolve object"); } } }
One important point about the writeObject()
method is that only a matching public static final
field name is written to the stream; any additional state is ignored. So what happens when a constant has an additional state as shown below?
public final class NumberConstants extends AbstractConstant{ public static final NumberConstants ONE=new NumberConstants("1"); //etc public String toString(){ return _rep; } private final String _rep; private NumberConstants(String rep){ _rep=rep; } }
We are dealing with static constants, so any additional state appears preserved because readResolve()
matches the constant's serialized representation with the local object reference. If for any reason a program changes state associated with a constant (i.e., a field isn't declared final
) between serializing and deserializing, then this technique fails. However, changing state associated with a constant means it's not a constant after all, and you probably shouldn't apply this idiom in the first place.
Final notes
Assuming you use typesafe constants in their canonical form, you can simply extend the AbstractConstant
class and happily write and use typesafe constants knowing they will work with serialization.
This technique also works with Jini entries (that have different serialization semantics) and therefore with JavaSpaces, where a key field in an Entry
is an instance of a class that extends the AbstractConstant
.
Finally, keep in mind that this solution applies to simple serialization and works with many distributed Java systems, but it does not address the multiple classloader problems identified in Roubtsov's article.
Learn more about this topic
- Download the associated source code
http://images.techhive.com/downloads/idge/imported/article/jvw/2003/01/jw-javatip133.zip - "Java Tip 122Beware of Java Typesafe Enumerations," Vladimir Roubtsov (JavaWorld, January 2002)
http://www.javaworld.com/javaworld/javatips/jw-javatip122.html - "Java Tip 27Typesafe Constants in C++ and Java," Philip Bishop (JavaWorld, 1997)
http://www.javaworld.com/javaworld/javatips/jw-javatip27.html - Type safety and constants are covered in Java in PracticeDesign Styles and Idioms for Effective Java, Philip Bishop and Nigel Warren (Addison-Wesley, 1998; ISBN0201360659)
http://www.amazon.com/exec/obidos/tg/detail/-/0201360659/javaworld - Typesafe constants for JavaSpaces is covered in JavaSpaces in Practice, Philip Bishop and Nigel Warren (Addison-Wesley, 2002; ISBN0321112318)
http://www.amazon.com/exec/obidos/tg/detail/-/0321112318/javaworld - Other related JavaWorld articles:
- "How to Implement State-Dependent Behavior," Eric Armstrong (JavaWorld, August 1997)
- "Create Enumerated Constants in Java," Eric Armstrong (JavaWorld, July 1997)
- Links to other articles and code examples by Philip Bishop
http://www.javainpractice.net - For more insightful Java tips, visit JavaWorld's Java Tips index page
http://www.javaworld.com/columns/jw-tips-index.shtml - See the Java Q&A index page for the full Q&A catalog
http://www.javaworld.com/columns/jw-qna-index.shtml - Browse the Core Java section of JavaWorld's Topical Index
http://www.javaworld.com/channel_content/jw-core-index.shtml - Get more of your questions answered in our Java Beginner discussion
http://forums.devworld.com/webx?50@@.ee6b804 - Sign up for JavaWorld's free weekly Core Java email newsletter
http://www.javaworld.com/subscribe - You'll find a wealth of IT-related articles from our sister publications at IDG.net