Sunday, 11 March 2018

Item 44: Favor the use of standard functional interfaces

Now that Java has lambdas, best practices for writing APIs have changed considerably. For example, the Template Method pattern [Gamma95], wherein a subclass overrides a primitive method to specialize the behavior of its superclass, is far less attractive.  The modern alternative is to provide a static factory or constructor that accepts a function object to achieve the same effect. More generally, you’ll be writing more constructors and methods that take function objects as parameters. Choosing the right functional parameter type demands care.

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
   return size() > 100;
}

// Unnecessary functional interface; use a standard one instead.
@FunctionalInterface interface EldestEntryRemovalFunction<K,V>{
    boolean remove(Map<K,V> map, Map.Entry<K,V> eldest);
}

The java.util.function package provides a large collection of standard functional interfaces for your use. If one of the standard functional interfaces does the job, you should generally use it in preference to a purpose-built functional interface.

 The Predicate interface, for instance, provides methods to combine predicates. In the case of our LinkedHashMap example, the standard BiPredicate<Map<K,V>, Map.Entry<K,V>> interface should be used in preference to a custom EldestEntryRemovalFunction interface.

There are forty-three interfaces in java.util.Function. You can’t be expected to remember them all, but if you remember six basic interfaces, you can derive the rest when you need them.

The Operator interfaces represent functions whose result and argument types are the same. 
The Predicate interface represents a function that takes an argument and returns a boolean.
The Function interface represents a function whose argument and return types differ.
The Supplier interface represents a function that takes no arguments and returns(“supplies”) a value.
TheConsumer represents a function that takes an argument and returns nothing.


Interface
Function Signature
Example
UnaryOperator<T>
T apply(T t)
String::toLowerCase
BinaryOperator<T>
T apply(T t1, T t2)
BigInteger::add
Predicate<T>
boolean test(T t)
Collection::isEmpty
Function<T,R>
R apply(T t)
Arrays::asList
Supplier<T>
T get()
Instant::now
Consumer<T>
void accept(T t)
System.out::println

There are also three variants of each of the six basic interfaces to operate on the primitive types intlong, and double. Their names are derived from the basic interfaces by prefixing them with a primitive type. So, for example, a predicate that takes an int is an IntPredicate, and a binary operator that takes two long values and returns a long is a LongBinaryOperator. None of these variant types is parameterized except for the Function variants, which are parameterized by return type. For example, LongFunction<int[]> takes a long and returns an int[].

There are nine additional variants of the Function interface, for use when the result type is primitive. The source and result types always differ, because a function from a type to itself is a UnaryOperator.

Most of the standard functional interfaces exist only to provide support for primitive types. Don’t be tempted to use basic functional interfaces with boxed primitives instead of primitive functional interfaces. While it works, it violates the advice of Item 61, “prefer primitive types to boxed primitives.” The performance consequences of using boxed primitives for bulk operations can be deadly.

There are several reasons that Comparator deserves its own interface.

  1. Its name provides excellent documentation every time it is used in an API, and it’s used a lot.
  2.  The interface is heavily outfitted with useful default methods to transform and combine comparators.
  3.  The Comparator interface has strong requirements on what constitutes a valid instance, which comprise its general contract. By implementing the interface, you are pledging to adhere to its contract.


You should seriously consider writing a purpose-built functional interface in preference to using a standard one if you need a functional interface that shares one or more of the following characteristics with Comparator:
• It will be commonly used and could benefit from a descriptive name.
• It has a strong contract associated with it.
• It would benefit from custom default methods.

If you elect to write your own functional interface, remember that it’s an interface and hence should be designed with great care (Item 21).
Always annotate your functional interfaces with the @FunctionalInterfaceannotation.

Do not provide a method with multiple overloadings that take different functional interfaces in the same argument position if it could create a possible ambiguity in the client. This is not just a theoretical problem. The submit method of ExecutorService can take either a Callable<T> or a Runnable, and it is possible to write a client program that requires a cast to indicate the correct overloading (Item 52). The easiest way to avoid this problem is not to write overloadings that take different functional interfaces in the same argument position. This is a special case of the advice in Item 52, “use overloading judiciously.”

In summary, now that Java has lambdas, it is imperative that you design your APIs with lambdas in mind. Accept functional interface types on input and return them on output. It is generally best to use the standard interfaces provided in java.util.function.Function, but keep your eyes open for the relatively rare cases where you would be better off writing your own functional interface.

No comments:

Post a Comment