/ JDK, FUNCTIONAL INTERFACES, DESIGN

Design gotchas in JDK's functional interfaces

Because of a course I prepared, I recently had a closer look at the java.util.function package in JDK 8, and discovered a couple of interesting design choices.

Callable is not a Supplier

Their respective definitions are:

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

Thus, a Callable and a Supplier are defined in the same way:

Callable<String> callable = () -> "Hello";
Supplier<String> supplier = () -> "Hello";

But they are not equivalent:

ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(callable); (1)
executorService.submit(() -> "Hello");(2)
executorService.submit(supplier); (3)
executorService.submit((Callable) supplier); (4)
1 OK
2 OK
3 Doesn’t compile
4 Throws at runtime

Comparator is not a BiFunction

Their respective definitions are:

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

@FunctionalInterface
public BiFunction<T, U, R> {
    R apply(T t, U u);
}

The same statements as above apply, but there’s one thing of note. No method throws a checked exception. Obviously, methods names are different, but Comparator could have been written in the following way:

public interface Comparator<T> extends BiFunction<T, T, Integer> {

    int compare(T o1, T o2);

    default Integer apply(T o1, T o2) {
        return compare(o1, o2);
    }
}

Yet, Comparator is a legacy class, so better not touch it.

Predicate is not a Function

Definitions:

@FunctionalInterface
public Predicate<T> {
    boolean test(T t);
}

@FunctionalInterface
public interface Function<T,R> {
    R apply(T t);
}

As above, this one could have been written as:

public interface Predicate<T> extends Function<T, Boolean> {

    boolean test(T t);

    default Boolean apply(T t) {
        return test(t);
    }
}

Or even better, keeping just one single method:

public interface Predicate<T> extends Function<T, Boolean> {
}

There might be two reasons for this lack of relationship:

  • The specialized method name (test instead of apply), but to be honest, that’s not a real issue since most Predicate instances will be written as lambdas anyway.
  • The boolean primitive return type - instead of Boolean, to prevent returning null. Guess what? Thanks to auto-boxing, it prevents nothing:
Predicate<Object> predicate = o -> (Boolean) null;

Conclusion

Java is a language that values backward-compatibility (e.g. generics and type erasure) - a lot. That means that whatever design choices are made, they will stay forever, or at least long enough to have consequences.

According to my understanding of Functional Programming, two functions are equivalent if, from the same input they return the same output. Naming is not relevant, and adds noise. While the 2 first lack of relationships described above can be justified by backward-compatibility, I find the last one contradicting that. Any thoughts?

Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Also double as a trainer and triples as a book author.

Read More
Design gotchas in JDK's functional interfaces
Share this