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
The
There are forty-three interfaces in
The
The
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
There are nine additional variants of the
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
int
, long
, 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.- Its name provides excellent documentation every time it is used in an API, and it’s used a lot.
- The interface is heavily outfitted with useful default methods to transform and combine comparators.
- 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
@FunctionalInterface
annotation.
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