Tuesday, July 13, 2004

Value objects with JDK1.5

Trying out generics, I made a generic value object with JDK 1.5:

/** * <code>ObjectValueBase</code> is the abstract base for simple comparable, * value types. * * @author <a href="mailto:binkley@alumni.rice.edu">B. K. Oxley (binkley)</a> * @version $Revision$ * @since 1.0 */ abstract class ObjectValueBase <T extends Comparable<T>, V extends ObjectValueBase<T, V>> implements Cloneable, Comparable<V> { private T value; /** * Constructs a new <code>ObjectValueBase</code> with the given * <var>value</var>. * * @param value the value * * @throws IllegalArgumentException if <var>value</var> is invalid */ protected ObjectValueBase(final T value) { if (!valid(value)) throw new IllegalArgumentException(); this.value = value; } /** * Gets the value of this value object. * * @return the value */ public T getValue() { return value; } /** * Validates that the given <var>value</var> is permitted for this * <code>ObjectValueBase</code>. Constructors validate input this way. By * default, all inputs are valid; subclasses should override to restrict * input. * * @param value the value */ protected abstract boolean valid(final T value); // Cloneable /** * {@inheritDoc} * * A typical implementation for class <code>Foo</code> which extends * <code>ObjectValueBase</code> is: <pre> public Foo clone() { * return new Foo(getValue()); * }</pre> */ @Override public abstract V clone(); // Comparable /** * {@inheritDoc} */ public int compareTo(final V that) { if (null == that) return -1; else if (null == getValue()) return null == that.getValue() ? 0 : -1; else return getValue().compareTo(that.getValue()); } // Object /** * After self and nullity tests, passes check for equality to the held * value. * * {@inheritDoc} */ @Override public boolean equals(final Object o) { if (this == o) return true; else if (!(o instanceof ObjectValueBase)) return false; else // pass equality test on to value, even for different types return equals((V) o); } private boolean equals(final V that) { if (null == that) return false; else if (null == getValue()) return null == that.getValue(); else return getValue().equals(that.getValue()); } /** * {@inheritDoc} */ @Override public int hashCode() { if (null == getValue()) return 0; else return getValue().hashCode(); } /** * {@inheritDoc} */ @Override public String toString() { if (null == getValue()) return null; else return getValue().toString(); } }

The chief drawback, however, is that Java generics do not support native primitives, unlike templates in C++ or generics in all-object languages such as Scheme. And most value objects I use wrap primitives such as int (any sort of small-scale numeric) or float (often money). So I copy the class, take out the generics, and fix the wrapped type to be the needed primitive. A lot like JDK 1.4, then, but at least I get covariant return types for clone().

UPDATED: I added the C++-trick of including a class as a template parameter of its base class. In this case, by including the class as a generic parameter to ObjectValueBase, I can specify that clone() returns the correct type, no casts required. Neato!

No comments: