Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Design Gotchas in the JDK's Functional Interfaces

DZone's Guide to

Design Gotchas in the JDK's Functional Interfaces

Let's dive into functional interfaces in Java to examine three interesting design choices and the likely reasons (or lack of reason) behind them.

· Java Zone ·
Free Resource

Learn how to build stream processing applications in Java-includes reference application. Brought to you in partnership with Hazelcast.

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 two first lack of relationships described above can be justified by backward-compatibility, I find the last one contradicting that. Any thoughts?

Learn how to build distributed stream processing applications in Java that elastically scale to meet demand- includes reference application.  Brought to you in partnership with Hazelcast.

Topics:
java ,functional interface ,jdk ,functional programming

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}