Java SE 8's New Language Features, Part 2: Predefined Functional Interfaces, Method References, and More
- Predefined Functional Interfaces
- Method References
- Enhanced Generic Type Inference / Type Annotations
In Part 1 of this two-part series on new language features in Java 8, I introduced you to interface default and static methods as well as lambda expressions (anonymous methods) and functional interfaces. This article completes the series by introducing you to the java.util.function package of predefined functional interfaces, method (and constructor) references, enhanced generic type inference, and type annotations.
I developed this article's applications with the 64-bit version of JDK 8 build 132 on a Windows 7 platform. You can download the code from this article here.
Predefined Functional Interfaces
In Part 1, I mentioned that lambdas simplify the use of interfaces that declare single abstract methods, and that these interfaces are known as functional interfaces. I presented Runnable as an example of a functional interface located in the standard class library, and I discussed custom Converter and Justifier functional interfaces.
Some kinds of functional interfaces are more commonly used than others. To help you avoid reinventing the wheel by repeatedly introducing the same kind of functional interface, Oracle has added to Java 8 the java.util.function package of commonly used functional interfaces, including the following two examples:
- Predicate<T>: Represent a predicate (Boolean-valued function) of one argument. The lambda must conform to the parameter and return types of this interface's boolean test(T t) single abstract method.
- Consumer<T>: Represent an operation that accepts a single input argument and returns no result. The lambda must conform to the parameter and return types of this interface's void accept(T t) single abstract method. Unlike most of java.util.function's other functional interfaces, Consumer is expected to operate via side-effects.
Listing 1 presents an application that demonstrates Predicate and Consumer.
Listing 1 PCDemo.java
import java.util.function.Consumer; import java.util.function.Predicate; class Salesperson { private String name; private int salary; private int numsales; Salesperson(String name, int salary, int numsales) { this.name = name; this.salary = salary; this.numsales = numsales; } void addBonus(int amount) { salary += amount; } int getNumSales() { return numsales; } @Override public String toString() { return "name: " + name + ", salary: " + salary + ", numsales: " + numsales; } } public class PCDemo { public static void main(String[] args) { Salesperson[] salespersons = { new Salesperson("John Doe", 40000, 549), new Salesperson("Jane Doe", 39000, 1500) }; for (Salesperson salesperson: salespersons) { applyBonus(salesperson, salesperson_ -> salesperson_.getNumSales() > 1000, salesperson_ -> salesperson_.addBonus(2500)); System.out.println(salesperson); } } public static void applyBonus(Salesperson salesperson, Predicate<Salesperson> predicate, Consumer<Salesperson> consumer) { // Use predicate to determine whether or not to add bonus. // Use consumer to add the bonus if (predicate.test(salesperson)) consumer.accept(salesperson); } }
Listing 1 presents a Salesperson class that describes a salesperson in terms of name, salary, and number of sales. Along with a constructor, this class presents methods for adding a bonus (presumably based on number of sales exceeding a threshold), returning the number of sales, and returning a string representation of a Salesperson instance.
The PCDemo class in Listing 1 demonstrates the Predicate and Consumer functional interfaces. This class provides a main() method that creates an array of two Salesperson instances and iterates over this array to apply a bonus to eligible salespersons. PCDemo also provides an applyBonus() method.
The applyBonus() method is declared with three parameters having Salesperson, Predicate<Salesperson>, and Consumer<Salesperson> types. Arguments are passed to the final two parameters via lambdas whose compiler-inferred target types match these parameter types. (Each lambda's formal type parameter list specifies salesperson_ instead of salesperson because a formal type parameter list cannot introduce a new local variable with the same name as an existing variable in the enclosing scope.)
Consider predicate lambda salesperson_ -> salesperson_.getNumSales() > 1000. This lambda matches the Predicate<T> (with a Salesperson actual type argument) functional interface's boolean test(T t) single abstract method parameter and return types. Similarly, consumer lambda salesperson_ -> salesperson_.addBonus(2500) matches the Consumer<T> (with a Salesperson actual type argument) functional interface's void accept(T t) single abstract method parameter and return types.
When applyBonus() is invoked, the current salesPerson instance is passed to this method as its first argument. Furthermore, an instance of an implementation of the Predicate<Salesperson> functional interface that executes salesperson_.getNumSales() > 1000 is passed to this method as its second argument, and an instance of an implementation of the Consumer<Salesperson> functional interface that executes salesperson_.addBonus(2500) is passed to this method as its third argument.
Within applyBonus(), if (predicate.test(salesperson)) executes the predicate instance's test() method, which has been implemented to execute return salesperson.getNumSales() > 1000;, with applyBonus()'s salesperson argument. If test() returns true, consumer.accept(salesperson); executes, which has been implemented to execute salesperson.addBonus(2500);. The bonus is added to the salesperson who achieved more than 1000 sales.
Compile Listing 1 as follows:
javac PCDemo.java
Run the PCDemo application as follows:
java PCDemo
You should observe the following output:
name: John Doe, salary: 40000, numsales: 549 name: Jane Doe, salary: 41500, numsales: 1500