Java 8 functional interfaces

Getting to know various out-of-the-box functions such as Consumer, Predicate, Supplier

In the first part of this series, we learned that lambdas are a type of functional interface – an interface with a single abstract method. The Java API has many one-method interfaces such as Runnable, Callable, Comparator, ActionListener and others. They can be implemented and instantiated using anonymous class syntax. For example, take the ITrade functional interface. It has only one abstract method that takes a Trade object and returns a boolean value – perhaps checking the status of the trade or validating the order or some other condition.

In order to satisfy our requirement of checking for new trades, we could create a lambda expression, using the above functional interface, as shown here:

The expression is expecting a Trade instance as an input argument, declared to the left of the arrow token (we could omit the type of the input). The right side of the expression is simply the body of the check method – checking the status of the passed in Trade. The return type is implicitly boolean type as the check method returns boolean. The real power of using lambdas comes when you start creating a multitude of them representing real-world behavioral functions. For example, in addition to what we’ve already seen, here are the lambda expressions for finding out big trade (i.e, if the trade’s quantity is greater than 1 million) or checking out a newly created large Google trade:

These functions can then be passed on to a method (most probably server side) which takes in an ITrade as one of its parameters. Let’s say, we have a collection of trades and wish to filter out some trades based on a certain criteria. This requirement can be easily expressed using the above lambda passing to a method which accepts a list of trades too:

The above filterTrades method expects a collection trade and a lambda to be applied on each of the trades in the collection one by one, by iterating over them. If the input trade satisfies the pre-conditions defined by the lambda, the trade is added to the cumulative basket, else thrown away. The good thing about this method is that it accepts any lambda as long as it implements an ITrade interface. The method is shown below by taking the various lambdas:

Pre-Built functions library

There are a lot of re-usable functional requirements that can be captured by functional interfaces and lambdas. The designers of Java 8 have captured the common use cases and created a library of functions for them. A new package called java.util.function was created to host these common functions. In our ITrade case, all we’re doing is checking the boolean value of business functionality based on a condition. You can test a condition on any other object (not just on Trades). For example, you can check whether an employee is a member of a long list of employees; whether your train from London to Paris running on time; whether today is a sunny day, etc. The overall goal is to check for a condition and return true or false based on this. Taking the commonality of such use cases into account, Java 8 introduced a functional interface called Predicate that does exactly what we’re doing with ITrade – checking an input for its correct (true/false) value. Instead of writing our own functional interfaces, we can use the standard library of well defined, multi-faceted functional interfaces. There are a handful of these interfaces added to the library, which we’ll discuss in the next section.

java.util.Predicate

We need a function for checking a condition. A Predicate is one such function accepting a single argument to evaluate to a boolean result. It has a single method test which returns the boolean value. See the interface definition below which is a generic type accepting any type T:

From our knowledge of lambdas so far, we can deduce the lambda expression for this Predicate. Here are some example lambda expressions:

The invocation of the functionality is similar to what we’ve seen in the ITrade case. The Predicate‘s test is invoked to check the true vale, as shown in the following code snippet:

java.util.Function

A Function is a functional interface whose sole purpose is to return any result by working on a single input argument. It accepts an argument of type T and returns a result of type R, by applying specified logic on the input via the apply method. The interface definition shown here:

We use a Function for transformation purposes, such as converting temperature from Centigrade to Fahrenheit, transforming a String to an Integer, etc:

Did you notice the two arguments to the FunctionString and Integer? This indicates that the function is expecting a String and returning the Integer. A bit more sophisticated requirement, like aggregating the trade quantities for a given list of trades, can be expressed as a Function, shown below:

The aggregation of the above trades can be done using Stream API’s “fluent” style:

Other functions

Java 8 provides few other functions out of the box, such as Consumer, Supplier and others. The Consumer accepts a single argument but does not return any result:

This is mostly used to perform operations on the arguments such as persisting the employees, invoking house keeping operations, emailing newsletters etc. The Supplier, as the name suggests, supplies us with a result; here’s the method signature:

For example, fetching configuration values from database, loading with reference data, creating an list of students with default identifiers etc, can all be represented by a supplier function. On top of these functions, Java 8 provides specialized versions of these functions too for specific use cases. For example, the UnaryOperator which extends a Function acts only on same types. So, if we know that both the input and output types are the same, we could use UnaryOperator instead of Function.

Did you notice that the function is declared with one generic type? In this case, both being the same String – so, instead of writing Function, we managed to shorten it even further by using +UnaryOperator. Going a bit further, functions are also provided to represent primitive specializations, DoubleUnaryOperator, LongUnaryOperator, IntUnaryOperator etc. They basically deal with operations on primitives such as a double to produce a double or long to return a long, etc. Just like Function has children to accommodate special cases, so do other functions like IntPredicate or LongConsumer or BooleanSupplier, etc. For example, IntPredicate represents an integer value based function, LongConsumer expects a long value returning no result, and BooleanSupplier supplies boolean values. There are plethora of such specializations, so I would strongly advocate that you run through the API in detail to understand them.

Two argument functions

Until now, we have dealt with functions that only accept a single input argument. There are use cases that may have to operate on two arguments. For example, a function that expects two arguments but produces a result by operating on these two arguments. This type of functionality fits into two-argument functions bucket such as BiPredicate, BiConsumer, BiFunction, etc. They are pretty easy to understand too except that the signature will have an additional type (two input types and one return type). For instance, the BiFunction interface definition is shown below

The above function has three types – T, U and R. The first two are are input types while the last one is the return result. For completeness, the following example provides a snippet of BiFunction usage, which accepts two Trades to produce sum of trade quantities. The input types are Trade and return type is Integer:

Going with the same theme of two argument functions, the BiPredicate expects two input arguments and returns a boolean value (no surprise!):

As you may have guessed by now, there are specializations of these two-argument functions too. For example a function that operates on the same type of operands and emits the same type. Such facility is captured in BinaryOperator function. Note that the extends BinaryOperator extends BiFunction. The following example shows BinaryOperator in action – it accepts two Trades to produce a merged trade (which is of the same type as the input arguments – remember, the BinaryOperator is a special case of BiFunction).

Note that we did not pass in all three arguments when declaring the type (expectation is that all the inputs and outputs are of same type). Also, did you notice that the actual logic is carried out by the super function BiFunction rather than BinaryOperator? This is because the BinaryOperator extends the BiFunction:

Now that we have seen the functions and functional interfaces, there is something I would like to reveal about interfaces. Until Java 8, the interfaces were abstract creatures – you certainly cannot add implementation to it, making it brittle in some ways. However, Java 8 re-engineered this, calling them virtual methods.

Virtual (default) methods

The journey of a Java library begins with a simple interface. Over time, these libraries are expected to evolve and grow in functionality. However, pre-Java 8 interfaces are untouchable! Once they are defined and declared, they are practically written in stone. For obvious reasons, backward compatibility being the biggest one, they cannot be changed after the fact. While it’s great that lambdas have been added to the language, there’s no point of having them if they cannot be used with existing APIs. In order to add support to absorb lambdas into our libraries, the interfaces needed to evolve. That is, we need to be able to add additional functionality to an already published API. The dilemma is how to embrace lambdas to create or enhance the APIs without losing backward compatibility? This requirement pushed Java designers to come up with yet another elegant feature – providing virtual extension methods or simply default methods to the interfaces. This means we can create concrete methods in our interfaces going forward. The virtual methods allow us to add newer functionality too. The collections API is one such example, where bringing lambdas into the equation has overhauled and enhanced the APIs. Let us walk through a simple example to demonstrate the default method functionality. Let’s assume that every component is said to have a name and creation date when instantiated. However, should the implementation doesn’t provide concrete implementation for the name and creation date, they would be inherited from the interface by default. For out example, the IComponent interface defines a set of default methods as shown below:

As you can see from the snippet above, the interfaces now have an implementation rather than just being abstract. The methods are prefixed with a keyword default to indicate them as the default methods.

Multiple inheritance

Multiple inheritance is not new to Java. Java has provided multiple inheritance of types since its inception. If we have an object hierarchy implementing various interfaces, there are a few rules help us understand which implementation is applied to the child class. The fundamental rule is that the closest concrete implementation to the subclass wins the inherited behavior over others. The immediate concrete type has the precedence over any others. Take, for example, the following class hierarchy.

So, both Person and Faculty interfaces provide default implementation for name. However, note that Faculty extends Person but overrides the behavior to provide its own implementation. For any class that implements both these interfaces, the name is inherited from Faculty as it is the closest subtype to the child class. So, if I have a Student subclass implementing Faculty (and Person), the Student’s getName() method prints the Faculty’s name:

However, there’s one important point to note. What happens if our Faculty class does not extend Person at all? In this case, Student class inherits name from both implementations, thus making the compiler moan. To make our compiler happy, in this case, we must provide the concrete implementation by ourselves. However, if you wish to inherit one of the super type’s behaviors, you can do so explicitly, as we see in the code snippet below.

There’s a special syntax to obtain the method from a super interface – using super-interface.super.method: Person.super.getName().

Method references

In a lambda expression, when we are calling an already existing method of an existing class or super class, method and class references come in handy. These are new utility features introduced in Java 8 in order to represent lambdas even more concisely and succinctly. Method references are shortcuts for calling existing methods. For example, take a class that has already a method that adds up two integers:

Because the method of adding the integers already exists, there’s no point of creating a lambda expression doing exactly the same. Hence, we refer to this existing method via a method reference (using double colon ::), when create a lambda for IAddable implementation:

Notice the this::addThemUp lambda expression in the code above. The this refers to an instance of the AddableTest class while the bit after the double colon is the call to pre-existing method. Also, take a note that the method reference doesn’t add the braces () at the end. Should you have another class that implements desired functionality via a static method, you can simply use its method in a lambda expression by using this feature of method references. See the example given below:

We can also use a constructor reference by simply calling the class’s constructor as Employee::new or Trade::new.

Summary

In this post, we learned more about functional interfaces and functions. We dived into understanding the various out-of-the-box functions such as Consumer, Predicate, Supplier, etc. We also looked at virtual methods and method references. In the next post in this series, we will look at the Stream API in detail.

tags: , , , , , , ,

Get the O’Reilly Programming Newsletter

Weekly insight from industry insiders. Plus exclusive content and offers.