A Functional Interface in Java is an interface that contains a single abstract method. Java 8 introduced Lambda expressions that work with functional interfaces.
Lambda Expressions are a powerful new mechanism introduced in Java 8. A Lambda expression represents a function or executable block of code. These expressions can be passed and returned from a method.
Lambda expressions are a powerful feature that enable programmers to raise the level of abstraction, by creating more reusable code. They can also reduce the amount of boilerplate code a programmer is required to write. For example, if you worked with Java GUI toolkits or android, it was common to create an anonymous inner class to realize a listener interface, which could then be called from framework code. Lambda expressions simplify these use cases through their concise syntax.
Moreover, lambda expressions allow us to create more reusable code, using a concise syntax. We can create a method that contains the general algorithm to achieve some task. We can parameterize this method with a functional interface, that represents the parts of the algorithm that vary. This method can then be called in different contexts by passing a lambda expression that represents an implementation of the functional interface argument.
For example consider starting a thread in Java. Before Java 8 and lambda expressions we would create an anonymous inner class:
1 2 3 4 5 6 7 8 | new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Start With Anonymous class");
}
}).start();
|
This resulted in a lot of boilerplate code that can be simplified with the use of lambda expressions introduced in Java 8.
1 | new Thread(() -> System.out.println("Start With Lambda")) .start();
|
A Functional Interface is an interface that contains one abstract method. For example, the Runnable interface we saw in the last example has the definition:
1 2 3 4 | @FunctionalInterface
public interface Runnable {
public abstract void run();
}
|
Also notice the inclusion of the @FunctionalInterface
annotation. Although not mandatory, it is recommended that a functional interface contains this annotation. The annotation not only makes it clear that any interface annotated with this annotation is intended to be a functional interface but also, the compiler will check that the annotated interface conforms to the rules of a functional interface.
The java.util.function package contains a number of functional interfaces for use with lambda expressions and method references. Some of these interfaces include:
The Function Functional Interface represents a function / method that takes a single parameter and returns a result. This interface is also generic and is parameterized with type parameters for the argument and return type.
1 2 3 | public interface Function<T, R> {
R apply(T t);
}
|
This interface is used throughout the standard Java API’s. For example, it is used with the Collectors.toMap method to convert a stream into a Map
1 2 3 | public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) { }
|
Below we use the above method to extract the even integers from a list and convert it to a Map<Integer,String>
.
1 2 3 4 5 6 7 8 | List<Integer> integerList = new LinkedList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
Map<Integer,String> evenIntegerMap = integerList.stream()
.filter((i) -> i % 2 ==0)
.collect(Collectors.toMap((k) -> k, (v) -> v.toString()));
|
Above the toMap
method is called with two lambda expressions of type Function<T, R>
. The first to return the key for each entry in the list of integers to be added to the Map.
1 | ((k) -> k
|
and the second to return the value to be added to the map.
1 | (v) -> v.toString()
|
There is also the BiFunction
interface. This interface is similar to the Function interface apart from it takes two arguments instead of one.
1 2 3 | @FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
|
The Supplier Functional Interface is typically used to supply a value to a method. It is also commonly used for lazy evaluation of values. The supplier interface has a single method get that takes zero parameters and returns a value.
1 2 3 4 | @FunctionalInterface
public interface Supplier<T> {
T get();
}
|
The Supplier Functional Interface is also a generic interface which takes a single type parameter, which is the type of the value returned. The Supplier Interface is also commonly found throughout the Java API’s.
For example the Objects utility class contains the method requireNonNull
which uses a Supplier
1 2 3 4 5 6 | public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {
if (obj == null)
throw new NullPointerException(messageSupplier == null ?
null : messageSupplier.get());
return obj;
}
|
The Consumer Functional Interface is again a generic interface that accepts an argument and returns nothing.
1 2 3 4 | @FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
|
Again consumers are commonly used throughout the Java API’s. For example, we can print the elements of list using the forEach method.
1 2 3 4 5 6 | List<Integer> integerList = new LinkedList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
integerList.forEach((i) -> System.out.println(i));
|
Above we pass a Consumer to the forEach method to print the elements of a list. There are also variations of the Consumer interface. One such variation is the BiConsumer, which is like the Consumer but it accepts two values.
1 2 3 4 | @FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
}
|
Below we use the BiConsumer to print the key and value of each element in a Map.
1 2 3 4 5 | Map<String, String> map = new HashMap<>();
map.put("1", "One");
map.put("2", "Two");
map.forEach((k, v) -> System.out.println(String.format("Key: %s, Value: %s", k, v)));
|
The Predicate Functional Interface is an interface that receives a value and returns a Boolean value.
1 2 3 4 5 | @FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
|
Predicates are typically used for filtering collections or testing some condition. For example, in the code below, we filter a list of integers to return only the even integers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @Test
public void predicate() {
List<Integer> integerList = new LinkedList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
List<Integer> expected = new LinkedList<>();
expected.add(2);
List<Integer> evenIntegers = integerList.stream()
.filter((i) -> i % 2 ==0)
.collect(Collectors.toList());
assertThat(evenIntegers, is(expected));
}
|
The filter method above takes a predicate that tests if an integer is even.
1 | .filter((i) -> i % 2 ==0)
|
Operator Functional Interfaces represent functional interfaces that accept and return a value of the same type.
There is the UnaryOperator interface that accepts a single argument and returns a value of the same type.
1 | public interface UnaryOperator<T> extends Function<T, T> {
|
For example, below we use the UnaryOperator interface to add one to each integer in a list.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @Test
public void unaryOperator() {
List<Integer> integerList = new LinkedList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
List<Integer> expected = new LinkedList<>();
expected.add(2);
expected.add(3);
expected.add(4);
integerList.replaceAll((i) -> i+1);
assertThat(integerList, is(expected));
}
|
The replaceAll
method receives a UnaryOperator
instance as a lambda expression. There are also variations of this interface for example the BinaryOperator which takes two arguments and returns a value of the same type.
1 | public interface BinaryOperator<T> extends BiFunction<T,T,T> { }
|
For example, below we use it to sum the integers in a list.
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Test
public void binaryOperator() {
List<Integer> integerList = new LinkedList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
Integer expected = 6;
Integer result = integerList.stream()
.reduce(0,(x,y) -> x + y);
assertEquals(expected, result);
}
|
The BinaryOperator is passed as a lambda expression to the reduce method.
1 | .reduce(0,(x,y) -> x + y);
|
In this tutorial we introduced Functional Interfaces in Java. We also looked at some of the most common functional interfaces provided by the Java standard API.