Tuesday, June 12, 2012

The generic getter idiom in Java

In our code base we represent Google protobuf messages as rooted trees, informally just trees. Nodes are necessarily heterogeneous: some are value nodes—strings, numbers, etc.—, some are roots of more trees, that is, node collections.

I recently replaced code like this:

StringNode childA = (StringNode) parent.get("child A's name");
Int32Node childB = (Int32Node) parent.get("child B's name");

With code like this:

StringNode childA = parent.get("child A's name");
Int32Node childB = parent.get("child B's name");

How did I do this? I changed the definition of "get" from:

public Node get(final String name) {
    // Clever implementation
}

To:

public <N extends Node> N get(final String name) {
    // Same implementation with some "(N)" casts
}

When the compiler can infer the return type of "get", it will, saving you writing now and reading later. In those cases where it cannot infer the type, you still help:

final String valueA = (String) ((StringNode) parent).get("child A's name").getValue();
final Integer valueB = (Integer) ((Int32Node) parent).get("child B's name").getValue();

Becomes:

final String valueA = parent.<StringNode>get("child A's name").getValue();
final Integer valueB = parent.<Int32Node>get("child B's name").getValue();

There is still a type cast, but you spell it differently using the generics system. Note "getValue" uses the same idiom as "get"; the return type can be inferred, no extra writing needed.

I'm going to call this pattern the Generic Getter Idiom until I find a good reference.

Related: Crossing generics and covariant returns

No comments: