Overload API methods with care

Overloading methods is a strong concept in API design, especially when your API is a fluent API or DSL (Domain Specific Language). This is the case for jOOQ, where you often want to use the exact same method name for various means of interaction with the library.

Example: jOOQ Conditions


package org.jooq;

public interface Condition {

    // Various overloaded forms of the "AND" operation:
    Condition and(Condition other);
    Condition and(String sql);
    Condition and(String sql, Object... bindings);

    // [...]
}

All of these methods connect two conditions with each other using an “AND” operator. Ideally, the implementations depend on each other, creating a single point of failure. This keeps things DRY:

package org.jooq.impl;

abstract class AbstractCondition implements Condition {

    // The single point of failure
    @Override
    public final Condition and(Condition other) {
        return new CombinedCondition(
            Operator.AND, Arrays.asList(this, other));
    }

    // "Convenience methods" delegating to the other one
    @Override
    public final Condition and(String sql) {
        return and(condition(sql));
    }

    @Override
    public final Condition and(String sql, Object... bindings) {
        return and(condition(sql, bindings));
    }
}

The trouble with generics and overloading

When developing with Eclipse, the Java 5 world seems more shiny than it really is. Varargs and generics were introduced as syntactic sugar in Java 5. They don’t really exist in that way in the JVM. That means, the compiler has to link method invocations correctly, inferring types if needed, and creating synthetic methods in some cases. According to the JLS (Java Language Specification), there is a lot of ambiguity when varargs/generics are employed in overloaded methods.

Let’s elaborate on generics:

A nice thing to do in jOOQ is to treat constant values the same as fields. In many places, field arguments are overloaded like this:

// This is a convenience method:
public static <T> Field<T> myFunction(Field<T> field, T value) {
    return myFunction(field, val(value));
}

// It's equivalent to this one.
public static <T> Field<T> myFunction(Field<T> field, Field<T> value) {
    return MyFunction<T>(field, value);
}

The above works very well in most of the cases. You can use the above API like this:

Field<Integer> field1  = //...
Field<String>  field2  = //...

Field<Integer> result1 = myFunction(field1, 1);
Field<String>  result2 = myFunction(field2, "abc");

But the trouble arises when <T> is bound to Object!

// While this works...
Field<Object>  field3  = //...
Field<Object>  result3 = myFunction(field3, new Object());

// ... this doesn't!
Field<Object>  field4  = //...
Field<Object>  result4 = myFunction(field4, field4);
Field<Object>  result4 = myFunction(field4, (Field) field4);
Field<Object>  result4 = myFunction(field4, (Field<Object>) field4);

When <T> is bound to Object, all of a sudden both methods apply, and according to the JLS, none of them is more specific! While the Eclipse compiler is usually a bit more lenient (and in this case intuitively links the second method), the javac compiler doesn’t know what to do with this call. And there is no way around it. You cannot cast field4 to Field or to Field<Object> to force the linker to link to the second method. That’s pretty bad news for an API designer. For more details about this special case, consider the following Stack Overflow question, which I reported as a bug to both Oracle and Eclipse. Let’s see which compiler implementation is correct: https://stackoverflow.com/questions/5361513/reference-is-ambiguous-with-generics

The trouble with static imports, varargs

There is more trouble for API designers, that I will document some other time.

Leave a Reply