Thou Shalt Not Name Thy Method “Equals”

(unless you really override Object.equals(), of course).

I’ve stumbled upon a rather curious Stack Overflow question by user Frank:

Why does Java’s Area#equals method not override Object#equals?

Interestingly, there is a Area.equals(Area) method which really takes an Area argument, instead of a Object argument as declared in Object.equals(). This leads to rather nasty behaviour, as discovered by Frank:

@org.junit.Test
public void testEquals() {
    java.awt.geom.Area a = new java.awt.geom.Area();
    java.awt.geom.Area b = new java.awt.geom.Area();
    assertTrue(a.equals(b)); // -> true

    java.lang.Object o = b;
    assertTrue(a.equals(o)); // -> false
}

Technically, it is correct for AWT’s Area to have been implemented this way (as hashCode() isn’t implemented either), but the way Java resolves methods, and the way programmers digest code that has been written like the above code, it is really a terrible idea to overload the equals method.

No static equals, either

These rules also hold true for static equals() methods, such as for instance Apache Commons Lang‘s

ObjectUtils.equals(Object o1, Object o2)

The confusion here arises by the fact that you cannot static-import this equals method:

import static org.apache.commons.lang.ObjectUtils.equals;

When you now type the following:

equals(obj1, obj2);

You will get a compiler error:

The method equals(Object) in the type Object is not applicable for the arguments (…, …)

The reason for this is that methods that are in the scope of the current class and its super types will always shadow anything that you import this way. The following doesn’t work either:

import static org.apache.commons.lang.ObjectUtils.defaultIfNull;

public class Test {
  void test() {
    defaultIfNull(null, null);
    // ^^ compilation error here
  }

  void defaultIfNull() {
  }
}

Details in this Stack Overflow question.

Conclusion

The conclusion is simple. never overload any of the methods declared in Object (overriding is fine, of course). This includes:

  • clone()
  • equals()
  • finalize()
  • getClass()
  • hashCode()
  • notify()
  • notifyAll()
  • toString()
  • wait()

Of course, it would be great if those methods weren’t declared in Object in the first place, but that ship has sailed 20 years ago.

You Will Regret Applying Overloading with Lambdas!

Writing good APIs is hard. Extremely hard. You have to think of an incredible amount of things if you want your users to love your API. You have to find the right balance between:

  1. Usefulness
  2. Usability
  3. Backward compatibility
  4. Forward compatibility

We’ve blogged about this topic before, in our article: How to Design a Good, Regular API. Today, we’re going to look into how…

Java 8 changes the rules

Yes!

Overloading is a nice tool to provide covenience in two dimensions:

  • By providing argument type alternatives
  • By providing argument default values

Examples for the above from the JDK include:

public class Arrays {

    // Argument type alternatives
    public static void sort(int[] a) { ... }
    public static void sort(long[] a) { ... }

    // Argument default values
    public static IntStream stream(int[] array) { ... }
    public static IntStream stream(int[] array, 
        int startInclusive, 
        int endExclusive) { ... }
}

The jOOQ API is obviously full of such convenience. As jOOQ is a DSL for SQL, we might even abuse a little bit:

public interface DSLContext {
    <T1> SelectSelectStep<Record1<T1>> 
        select(SelectField<T1> field1);

    <T1, T2> SelectSelectStep<Record2<T1, T2>> 
        select(SelectField<T1> field1, 
               SelectField<T2> field2);

    <T1, T2, T3> SelectSelectStep<Record3<T1, T2, T3>> s
        select(SelectField<T1> field1, 
               SelectField<T2> field2, 
               SelectField<T3> field3);

    <T1, T2, T3, T4> SelectSelectStep<Record4<T1, T2, T3, T4>> 
        select(SelectField<T1> field1, 
               SelectField<T2> field2, 
               SelectField<T3> field3, 
               SelectField<T4> field4);

    // and so on...
}

Languages like Ceylon take this idea of convenience one step further by claiming that the above is the only reasonable reason why overloading is be used in Java. And thus, the creators of Ceylon have completely removed overloading from their language, replacing the above by union types and actual default values for arguments. E.g.

// Union types
void sort(int[]|long[] a) { ... }

// Default argument values
IntStream stream(int[] array,
    int startInclusive = 0,
    int endInclusive = array.length) { ... }

Read “Top 10 Ceylon Language Features I Wish We Had In Java” for more information about Ceylon.

In Java, unfortunately, we cannot use union types or argument default values. So we have to use overloading to provide our API consumers with convenience methods.

If your method argument is a functional interface, however, things changed drastically between Java 7 and Java 8, with respect to method overloading. An example is given here from JavaFX.

JavaFX’s “unfriendly” ObservableList

JavaFX enhances the JDK collection types by making them “observable”. Not to be confused with Observable, a dinosaur type from the JDK 1.0 and from pre-Swing days.

JavaFX’s own Observable essentially looks like this:

public interface Observable {
  void addListener(InvalidationListener listener);
  void removeListener(InvalidationListener listener);
}

And luckily, this InvalidationListener is a functional interface:

@FunctionalInterface
public interface InvalidationListener {
  void invalidated(Observable observable);
}

This is great, because we can do things like:

Observable awesome = 
    FXCollections.observableArrayList();
awesome.addListener(fantastic -> splendid.cheer());

(notice how I’ve replaced foo/bar/baz with more cheerful terms. We should all do that. Foo and bar are so 1970)

Unfortunately, things get more hairy when we do what we would probably do, instead. I.e. instead of declaring an Observable, we’d like that to be a much more useful ObservableList:

ObservableList<String> awesome = 
    FXCollections.observableArrayList();
awesome.addListener(fantastic -> splendid.cheer());

But now, we get a compilation error on the second line:

awesome.addListener(fantastic -> splendid.cheer());
//      ^^^^^^^^^^^ 
// The method addListener(ListChangeListener<? super String>) 
// is ambiguous for the type ObservableList<String>

Because, essentially…

public interface ObservableList<E> 
extends List<E>, Observable {
    void addListener(ListChangeListener<? super E> listener);
}

and…

@FunctionalInterface
public interface ListChangeListener<E> {
    void onChanged(Change<? extends E> c);
}

Now again, before Java 8, the two listener types were completely unambiguously distinguishable, and they still are. You can easily call them by passing a named type. Our original code would still work if we wrote:

ObservableList<String> awesome = 
    FXCollections.observableArrayList();
InvalidationListener hearYe = 
    fantastic -> splendid.cheer();
awesome.addListener(hearYe);

Or…

ObservableList<String> awesome = 
    FXCollections.observableArrayList();
awesome.addListener((InvalidationListener) 
    fantastic -> splendid.cheer());

Or even…

ObservableList<String> awesome = 
    FXCollections.observableArrayList();
awesome.addListener((Observable fantastic) -> 
    splendid.cheer());

All of these measures will remove ambiguity. But frankly, lambdas are only half as cool if you have to explicitly type the lambda, or the argument types. We have modern IDEs that can perform autocompletion and help infer types just as much as the compiler itself.

Imagine if we really wanted to call the other addListener() method, the one that takes a ListChangeListener. We’d have to write any of

ObservableList<String> awesome = 
    FXCollections.observableArrayList();

// Agh. Remember that we have to repeat "String" here
ListChangeListener<String> hearYe = 
    fantastic -> splendid.cheer();
awesome.addListener(hearYe);

Or…

ObservableList<String> awesome = 
    FXCollections.observableArrayList();

// Agh. Remember that we have to repeat "String" here
awesome.addListener((ListChangeListener<String>) 
    fantastic -> splendid.cheer());

Or even…

ObservableList<String> awesome = 
    FXCollections.observableArrayList();

// WTF... "extends" String?? But that's what this thing needs...
awesome.addListener((Change<? extends String> fantastic) -> 
    splendid.cheer());

Overload you shan’t. Be wary you must.

API design is hard. It was hard before, it has gotten harder now. With Java 8, if any of your API methods’ arguments are a functional interface, think twice about overloading that API method. And once you’ve concluded to proceed with overloading, think again, a third time whether this is really a good idea.

Not convinced? Have a close look at the JDK. For instance the java.util.stream.Stream type. How many overloaded methods do you see that have the same number of functional interface arguments, which again take the same number of method arguments (as in our previous addListener() example)?

Zero.

There are overloads where overload argument numbers differ. For instance:

<R> R collect(Supplier<R> supplier,
              BiConsumer<R, ? super T> accumulator,
              BiConsumer<R, R> combiner);

<R, A> R collect(Collector<? super T, A, R> collector);

You will never have any ambiguity when calling collect().

But when the argument numbers do not differ, and neither do the arguments’ own method argument numbers, the method names are different. For instance:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);
IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);

Now, this is super annoying at the call site, because you have to think in advance what method you have to use based on a variety of involved types.

But it’s really the only solution to this dilemma. So, remember:

You Will Regret Applying Overloading with Lambdas!

Did you like this article? You might also like:

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:

http://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.