Functional interface
The functional interface is an interface (not a class or enum) with a single abstract method (SAM type). Static and default methods are allowed here.
There is a special annotation @FunctionalInterface exists in The Java Class Library. It marks functional interfaces and indicates if the interface doesn't satisfy the requirements of a functional interface (compile-time error). The annotation is not mandatory but it's recommended to mark functional interfaces.
Let's declare our own generic functional interface for representing a simple function with one argument. See code below.
@FunctionalInterface interface Func<T, R> { R apply(T val); static void doNothingStatic() { } default void doNothingByDefault() { } }
Implementing functional interfaces
Func<Long, Long> square = new Func<Long, Long>() {
@Override
public Long apply(Long val) {
return val * val;
}
};
long val = square.apply(10L); // the result is 100L
Func<Long, Long> square = val -> val * val; // the lambda expression
long val = square.apply(10L); // the result is 100L
Understanding lambda expressions
So, by lambda expression, we mean an anonymous function that allows to use a code as data and pass it as an argument to a method. Before the Java 8, many developers used anonymous classes for these purposes.
Any lambda expression has two parts: the part before the arrow operator "->" are the parameters and the part following the "->" is its body.
There are a lot of ways to write lambda expressions in Java 8. Let's consider some examples of lambda expressions using standard functional interfaces (included in the Java 8 Class Library).
// a simple way to write a lambda expression with two parameters
BiFunction<Integer, Integer, Integer> sum = (x, y) -> x + y;
// if it has only one argument "()" are optional
Function<Integer, Integer> identity1 = x -> x;
// it's valid too
Function<Integer, Integer> identity2 = (x) -> x;
// without type inference
Function<Integer, Integer> mult = (Integer x) -> x * 2;
// with multiple statements
Function<Integer, Integer> adder = (x) -> {
x += 5;
x += 10;
return x;
};
Standard functional interfaces will be studied in another topic, but here are some explanations:
- BiFunction<T, U, R> is a standard functional interface representing a function with two arguments of types T and U. It returns a value of type R.
- Function<T, R> is also a standard functional interface but it has only one argument of type T and returns a value of type R.
Sometimes, lambda expressions don't have parameters or return values. They will be studied later.
Passing lambda expressions to methods
It's possible to pass a lambda expression to a method if the method takes an object of type a suitable functional interface.
Here is an example. The method acceptFunctionalInterface takes an object of the standard type Function<Integer, Integer>.
public static void acceptFunctionalInterface(Function<Integer, Integer> f) {
System.out.println(f.apply(10));
}
Let's pass some functions in the method:
// it return the next value
Function<Integer, Integer> f = (x) -> x + 1;
acceptFunctionalInterface(f); // it prints 11
// or even without a reference
acceptFunctionalInterface(x -> x + 1); // the result is the same: 11
In other words, in Java, we can pass our functions (presented by objects) in a method/function as its arguments.
Note. In functional programming, a function (including methods in Java) that accepts or returns another function is called a higher-order function. A lot of features such as function composition, currying, monads and some others based on this idea.
Usage of closures
In the body of a lambda expression, it's possible to capture values from a context where the lambda is defined. This technique is called closure.
Let's see an example.
final String hello = "Hello, ";
Function<String, String> helloFunction = (name) -> hello + name;
System.out.println(helloFunction.apply("John"));
System.out.println(helloFunction.apply("Anastasia"));
The lambda expression captured the final variable hello.
The result of this code.
Hello, John
Hello, Anastasia
It's possible only if a context variable has a keyword final or it's effectively final, i.e. variable can't be changed. Otherwise, an error happens.
Let's consider the example with an effectively final variable.
int constant = 100;
Function<Integer, Integer> adder = x -> x + constant;
System.out.println(adder.apply(200));
System.out.println(adder.apply(300));
The variable constant is effectively final and being captured by the lambda expression.
Note. If we use anonymous classes instead of lambdas, we can do the same tricks.