- COMP.CS.140
- 8. Generics
- 8.3 Java’s functional interfaces
Java’s functional interfaces¶
We previously stated that “when we pass a lambda function as a parameter to a sorting function,
Java will automatically construct an object that implements Comparator
and whose member
function compare
performs the steps defined by our lambda function.”. This behaviour arises
from the fact that Comparator
is a so-called “functional interface”, and Java treats lambda
functions and function references as syntactic shortcuts to create objects that implement a
functional interface. We could think of lambda functions and function references as simplified
ways to express an anonymous class definition.
A functional interface in Java simply means an interface that has exactly one abstract member function. For example both of the next two example interfaces are functional interfaces because they have exactly one abstract function.
public interface SayHello {
public void sayHello(); // An abstract member function.
}
public interface Comparator<T> {
int compare(T a, T b); // An abstract member function.
boolean equals(Object obj); // Not abstract because an implementation is inherited from Object!
}
When a lambda function or a function reference appears in a context where an object that implements a certain funtional interface is expected, a Java compiler will automatically create an object that implements that interface. The implementation of the single abstract function required by the functional interface is based on the lambda function or the referenced function. From here on we will use the term “function object” to refer to an object that implements a functional interface.
Below is an example function helloTest
that takes as a parameter an object sh
of type
SayHello
. Because SayHello
is a functional interface, we may provide the parameter sh
using a lambda function or a function reference. It is of course possible to create the passed object
also as from a separately defined class or by using an anonymous class definition. The example below
uses each of the mentioned four ways to pass a SayHello
object to the function helloTest
.
public class FunctionalExample {
public static interface SayHello {
public void sayHello();
}
public static void helloTest(SayHello sh) {
sh.sayHello();
}
public static class MyHello implements SayHello {
@Override
public void sayHello() {
System.out.println("Hello from class MyHello!");
}
}
public static void myHelloFunction() {
System.out.println("Hello from a referenced function!");
}
public static void main(String[] args) {
// Option #1: define the parameter to helloTest as an object of class MyHello.
helloTest(new MyHello());
// Option #2: define the parameter to helloTest using an anonymous class definitoon.
helloTest(new SayHello() {
@Override
public void sayHello() {
System.out.println("Hello from an anonymous class!");
}
});
// Option #3: define the parameter to helloTest by using a lambda function.
helloTest(() -> System.out.println("Hello from a lambda-function!"));
// Option #4: define the parameter to helloTest by using a function reference.
helloTest(FunctionalExample::myHelloFunction);
}
}
The preceding code outputs:
Hello from class MyHello!
Hello from an anonymous class!
Hello from a lambda-function!
Hello from a referenced function!
When a Java compiler processes the part
helloTest(() -> System.out.println("Hello from lambda-function!"))
it can check that the
function helloTest
takes a function object of type SayHello
as a parameter. The compiler
can furthermore check that the interface SayHello
has exactly one abstract member function and
is thus a functional interface. This directs the compiler to automatically generate a similar
object as if we had used an anonymous class definition that implements SayHello
in such manner
that the function sayHello
performs the same steps as the lambda function. The part
helloTest(FunctionalExample::myHelloFunction)
would be processed otherwise in similar manner,
but now the function sayHello
would be based on the referenced function myHelloFunction
.
Just as in the case of anonymous class definitions, we may store a reference to an object created from a lambda function or a function reference. The next example is similar to a previous example about anonymous class definitions, but now a lambda function is used instead.
Point2D[] ps = {new Point2D(2.5, -1.5), new Point2D(-3.5, 4.5), new Point2D(0.5, 0.5)};
Comparator<Point2D> comparator = (Point2D a, Point2D b) -> { // Lambda function.
int cmp = Double.compare(a.getX(), b.getX());
if(cmp == 0) {
cmp = Double.compare(a.getY(), b.getY());
}
return cmp;
};
Arrays.sort(ps, comparator); // Give the comparator object "comparator" as a parameter.
for(Point2D p : ps) {
System.out.format(" (%.1f, %.1f)", p.getX(), p.getY());
}
Like discussed, we may use a lambda function or a function reference to automatically create a function object that implements an interface with exactly one abstract function. Here we naturally need to define the lambda function in such manner that it is compatible with the abstract function (the function parameters and the return type). Note that a lambda function or a function reference does not in itself express what interface an object created from it will implement: the compiler will infer the interface from the context, which usually means the type of the variable (including a function parameter) that will eventually refer to the created object.
Java class library contains also many other functional interfaces in addition to the already
familiar Comparator
interface. Some of them are introduced below. Their definitions are
presented in concise manner: only the abstract member function declaration is shown. The interfaces
may in addition contain e.g. default functions.
// The interface Predicate defines a "test", that is, a function that
// takes a parameter and returns a truth value true / false based on it.
public interface Predicate<T> {
boolean test(T t); // The function "test" checks if the parameter t fulfills some condition?
}
// The interface Function defines a function that takes
// one parameter of type T and returns a result of type R.
public interface Function<T,R> {
R apply(T t); // The function "apply" implements the function logic.
}
// The interface Unaryperator defines a function that takes
// one parameter of type T and returns a result of the same type T.
// UnaryOperator inherits the interface Function and the abstract
// function "apply" is actually declared by the interface Function.
public interface UnaryOperator<T> extends Function<T,T> {
T apply(T t); // The function "apply" implements the function logic.
}
// The interface BiFunction defines a function that takes two
// parameters of types T and U and returns a result of type R.
public interface BiFunction<T,U,R> {
R apply(T t, U u); // The function "apply" implements the function logic.
}
// The interface Binaryperator defines a function that takes two
// parameters of type T and returns a result of the same type T.
// BinaryOperator inherits the interface BiFunction and the abstract
// function "apply" is actually declared by the interface BiFunction.
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
T apply(T t, T u); // The function "apply" implements the function logic.
}
// The interface Consumer defines a function that takes one parameter
// of type T and returns no result (in some sense "consumes" the parameter).
public interface Consumer<T>
void accept(T t); // The function "accept" implements the function logic.
}
// The interface BiConsumer defines a function that takes two parameters of types
// T and U and returns no result (in some sense "consumes" the parameters).
public interface BiConsumer<T>
void accept(T t, U u); // The function "accept" implements the function logic.
}
// The interface Supplier defines a function that takes no parameters
// and returns a result of type R (in some sense "supplies" a new value).
public interface Supplier<R>
R get(); // The function "get" implements the function logic.
}
When you implement function that takes some kind of a function object as a parameter, it is often feasible to use one of Java’s readily available functional interfaces to define the parameter’s type (if the parameters and return type of the interface’s abstract member function are suitable).
Below is en example function filter
that takes a list container list
and a function object
implementing the interface Predicate
as parameters, and returns an ArrayList
that contains
each item t
of list
for which the function call predicate.test(t)
returns true
. A
technical detail is that the wildcard of the parameter predicate
allows to use a Predicate
object that can test an object of type T
or its supertype.
// A static generic function that takes a type parameter T.
public static <T> ArrayList<T> filter(List<T> list, Predicate<? super T> predicate) {
ArrayList<T> result = new ArrayList<>();
for(T t : list) {
if(predicate.test(t)) { // Does item t pass the test predicate.test(t)?
result.add(t); // If it did, insert t into the result list.
}
}
return result;
}
Below is an example of using filter
to select words with a certain length (the example uses
length 5) from a list of words.
// A word list for testing the "filter" function.
List<String> words = List.of("one", "two", "three", "four", "five", "six", "seven");
System.out.format("Original list: %s%n", words);
ArrayList<String> len5words = filter(words, w -> w.length() == 5); // Test: is length 5?
System.out.format("Filtered list: %s%n", len5words);
This code would output:
Original list: [one, two, three, four, five, six, seven]
Filtered list: [three, seven]
The preceding example function could be developed further to e.g. allow the caller to define some
kind of a transformation that should be applied to the items before they are put into the result.
The example above added items simply by doing result.add(t)
, but we could now do something like
result.add(mapper.apply(t))
instead. Here mapper
would be a second function object received
as a function parameter, and its member function apply
would perform the item transformation.
If we look at the previous list of Java functional interfaces, the interface Function
would
seem suitable: its function apply
takes one parameter and returns a value. The function
filterMap
given below works in this manner. The type parameter T
is the item type of the
original list and R
is the item type of the result list. The wildcard ? extends R
of the
parameter mapper
is based on the fact that a list containing objects of type R
can well
store also objects of some subtype of R
.
public static <T,R> ArrayList<R> filterMap(List<T> list, Predicate<? super T> predicate,
Function<? super T,? extends R> mapper) {
ArrayList<R> result = new ArrayList<>();
for(T t : list) {
if(predicate.test(t)) {
result.add(mapper.apply(t)); // Add t transformed by mapper into the result list.
}
}
return result;
}
We could use filterMap
e.g. to select words that represent integers and return them in an
Integer
list:
// Some words for testing, where some words represent integers.
List<String> words = List.of("one", "2", "three", "4", "five", "six", "7");
System.out.format("Original list: %s%n", words);
ArrayList<Integer> ints = filterMap(words, w -> { // Predicate checks if represents a number.
try{
Integer.parseInt(w);
}
catch(NumberFormatException e){
return false;
}
return true;
},
w -> Integer.parseInt(w)); // Function transforms w into a number.
System.out.format("Filtered list: %s%n", ints);
This code would output:
Original list: [one, 2, three, 4, five, six, 7]
Filtered list: [2, 4, 7]