Friday, October 01, 2004

Safer collections

In my post on IndexMap I mentioned in passing SafeHashMap. What is that?

The JDK is very useful but has some warts. One of the worst is this inconsistency: if you try to index into a list with a non-existent index (i.e., beyond the end of the list) the collections throw IndexOutOfBoundsException. But what happens when you try to fetch from a map with a non-existent key? The JDK map implementations silent insert a null value into the map for you and return it. This leads to the following anti-idiom:

Value getValue(Map map, Key key) {
    return (Value) map.get(key);
}

See the problem? Now all code dealing with map anywhere in the program needs to test for null values and decide how to handle them, or else be happy with NullPointerException:

for (Iterator it = map.values().iterator(); it.hasNext(); ) {
    Value value = (Value) it.next();

    if (null == value)
        handleNullValue();
    else
        doTheRealWorkWhichIsTheWholePoint(value);
}

Try coding that 10 times real fast.

What is the solution? Force the correct idiom in the first code fragment:

Value getValue(Map map, Key key) {
    if (!map.containsKey(key))
        map.put(key, createMissingValue(key));

    return (Value) map.get(key);
}

And to enforce this idiom, extend a concrete class such as HashMap with a safe wrapper, hence SafeHashMap, and forbid missing keys or null inserts:

public Object get(Object key) {
    if (null == key) throw new NullPointerException();
    if (!containsKey(key)) throw new IllegalArgumentException();

    return super.get(key);
}

public Object put(Object key, Object value) {
    if (null == key) throw new NullPointerException();
    if (null == value) throw new NullPointerException();

    return super.put(key, value);
}
Post a Comment