10 minutes
Lambdas and Functional interfaces in Java 8
In the previous post we saw an overview of what functional programming is and how the new features of Java 8 allow developers to write their applications using a more functional style. One of the main points in this new version of the language was the introduction of lambdas. Together with lambdas came the use of functional interfaces and methods references. This post will explore these features in more detail, showing when to use them, the restrictions around them and how you can use them to make your code more readable and concise.
Lambdas
First things first, what is a lambda (or lambda expression)? A lambda is an anonymous method that doesn’t have a name but it has a list of parameters, a body, a return type and potentially a list of exception that the lambda can throw. Unlike regular class methods, lambdas are not actually associated with any class. They can also be assigned to variables or passed as arguments to other methods. The name lambda expression comes from the field of mathematics.
We saw an example of a lambda expression in the
previous post, using an example
from the File
class to list csv
files:
File[] csvFiles = new File(".")
.listFiles(pathname -> pathname.getAbsolutePath().endsWith("csv"));
Here, we are passing a lambda expression to the listFiles
method that takes
one input parameter and returns a boolean value. I also mentioned that you can
assign lambdas to variables, so the previous code is functionally equivalent to:
FileFilter csvFilter = pathname -> pathname.getAbsolutePath().endsWith("csv");
File[] csvFiles = new File(".").listFiles(csvFilter);
How did we use to do that before Java 8? Like this:
File[] csvFiles = new File(".").listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getAbsolutePath().endsWith("csv");
}
});
You have to admit that the snippet using a lambda expression looks much more
concise and cleaner. In the last snippet we have to create an anonymous class
with an accept
method (with all the verbosity that it implies). In the first
one, we just need to specify our logic.
This brings an interesting question, if the listFiles
method takes a parameter
of FileFilter
type (which is an interface), how come we can pass a lambda
instead? We can do this because the FileFilter
interface is a functional
interface.
Functional interfaces
In a nutshell, a functional interface is an interface that specifies exactly
one abstract method. So the FileFilter
interface we saw before is
specified as:
@FunctionalInterface
public interface FileFilter {
boolean accept(File pathname);
}
Another example, is the Runnable
interface:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
You can see that both of these interfaces have a @FunctionalInterface
annotation. So what does that do? First it informs people who look at that
interface that it is intended to be a functional interface and that they can use
lambdas and method references wherever they are expected. Second, it works as a
compile-time check to make sure that the interface is indeed functional. If you
add this annotation to your interface and it is not in fact functional then you
will get a nice compile error letting you know this. It is worth noting that the
annotation is not actually required but it is usually a good idea to have it
there for the reasons I mentioned before.
There’s one small caveat here. We briefly discussed default methods in the previous post, which are methods whose implementation code can be written in an interface. Default methods do not count for the “exactly one abstract method” rule of functional interfaces so you can effectively have a functional interface with one abstract method and one or more default methods.
Some useful functional interfaces
Now that we know what a functional interface is and how it can be used, lets
look at some pretty useful interfaces provided by Java in its
java.function.util
package
Predicate
The predicate interface defines a simple test
method that takes an object and
returns a boolean. It looks something like:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
This is pretty useful for things like filtering. You could have a generic method to filter a list (this is just an example, you don’t need to write this logic and we’ll see why when we go into streams):
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T elem : list) {
if (predicate.test(elem)) {
result.add(elem);
}
}
return result;
}
And then create a predicate for your particular object. For example, a predicate
that given a User
returns true if his age is greater than or equal to 18:
public enum Sex {
MALE, FEMALE
}
public class User {
private final int age;
private final String name;
private final Sex sex;
public User(int age, String name, Sex sex) {
this.age = age;
this.name = name;
this.sex = sex;
}
public int getAge() {
return age;
}
public boolean isMale() {
return MALE.equals(sex);
}
}
Predicate<User> predicate = user -> user.getAge() >= 18;
A pretty useful functionality about predicates is that they can be composed
together to form more complex ones. For instance, what do you do if suddenly you
want a new User
predicate that returns true for all users who are less than
18? Do you create a new predicate like the previous one but changing the >=
by
<
? Luckily, you don’t have to because the Predicate
interface provides 3
methods to compose several predicates: and
, or
and negate
. So the previous
example could be written as:
Predicate<User> older = user -> user.getAge() >= 18;
Predicate<User> younger = older.negate();
Similarly, if we want a predicate that returns true for all the male users older or equal to 18, we could write it as:
Predicate<User> older = user -> user.getAge() >= 18;
Predicate<User> adultMales = older.and(User::isMale);
That last example shows that we can use method references where a Predicate
is
expected. In fact, we can use a method reference wherever a functional interface
is expected. We quickly saw method references in the
previous post but we’ll discuss more
about them later on.
Function
The java.util.function.Function interface is defined as:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
What this basically does is take an input of type T
and transform it somehow
to return an object of type R
. Note that the Predicate
interface can be seen
as a special case of a Function
where R
is always a boolean value. Following
our User
examples, imagine we want a function that given an User
instance it
returns that user’s name length. We could write this function like this:
Function<User,Integer> nameLength = user -> user.getName().length();
Like predicates, the Function
interface also has some useful methods to
compose several functions. The two methods offered are compose
and andThen
.
The difference between them is subtle but important. To understand this better,
imagine we have the following 2 functions:
Function<Integer,Integer> sumOne = number -> number + 1;
Function<Integer,Integer> duplicate = number -> number * 2;
We can then create 2 new functions in the following way:
Function<Integer, Integer> composed = sumOne.compose(duplicate);
Function<Integer, Integer> andThen = sumOne.andThen(duplicate);
System.out.println(composed.apply(2));
System.out.println(andThen.apply(2));
The composed
function will first apply duplicate
and then apply sumOne
on
the result. In other words, composing sumOne
with duplicate
will result in
sumOne(duplicate(x))
and the first System.out will print 5. The andThen
function will do exactly the opposite, it will first apply sumOne
and then
apply duplicate
on the result. In this case the second System.out will print
6.
Consumer
The java.util.function.Consumer interface defines an accept
method that takes
a paramter of type T
and returns no value. In other words:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
This interface is useful when you want to access an element and perform some
operation on it. For instance, starting with Java 8, lists have a forEach
method where you can pass a Consumer<T>
and this function will be applied to
each element on the list.
So imagine that you want to print to System.out
each element on a list. You
could do that in the following way:
List<String> users = Arrays.asList("java","8","rocks");
users.forEach(elem -> System.out.println(elem));
The implementation of the forEach
method is actually quite straightforward:
void forEach(Consumer<? super T> action) {
for (T t : this) {
action.accept(t);
}
}
Primitive functional interfaces
We saw a couple of generic, quite useful functional interfaces provided by the
language: Predicate<T>
, Function<T,R>
and Consumer<T>
. This is great for most
cases where you want to use this interfaces for your own classes. But what
happens when you need something like this for primitive types: int
, double
or boolean
for instance?
In Java, each primitive type has a corresponding wrapper
class. So
int
has an Integer
class and boolean
has a Boolean
. Additionally, Java
can handle conversions between these types for you automatically. This concept,
known as
autoboxing/unboxing
is what allows you to write code like this:
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < 10; i++) {
numbers.add(i);
}
This lets the developer write less code because he doesn’t need to worry about explicitly converting one type to the other. However, there is a performance impact involved. Is probably not a big deal if you do it occasionally here and there but when you are doing a boxing or unboxing on every iteration in a big list you will see a difference.
Going back to our functional interfaces, say you want to define a predicate that
takes an int
and returns a boolean
telling us whether the number is odd or
not. You can not define a Predicate<int>
because int
is not a class but you
could do something like this:
Predicate<Integer> isOdd = i -> i % 2 == 1;
isOdd.test(15);
What happens when you call this predicate with an int
is that this parameter
gets autoboxed into an Integer
. Again, this might not really be an issue if
you are not using this Predicate
in critical areas of your application or
inside big loops.
If you don’t want your parameters boxed automatically for you and want to really
use primitive types instead, Java 8 provides primitive specializations of its
functional interfaces. In our example, we could use the IntPredicate
interface, whose accept
method only takes int
parameters:
@FunctionalInterface
public interface IntPredicate {
boolean test(int value);
}
Therefore, our previous example could be rewritten as:
IntPredicate isOdd = i -> i % 2 == 1;
isOdd.test(15);
Now, the parameter to the test
method is treated as a primitive int
all the
way avoiding boxing and unboxing operations.
This primitive specializations extend to other types with similar names. So you
are going to find DoublePredicate
, IntFunction
, LongConsumer
and so on.
Method references
Lambda expressions are undoubtedly a great construct to make your code more compact. However, some times all you do in your lambda is to call an individual method potentially passing some parameter to it. In these cases you can often replace your lambda expression by a method reference.
Method references are compact ways to create lambda expressions for methods that
already have a name. For instance, in the previous section we saw an example of
the forEach
method:
List<String> users = Arrays.asList("java","8","rocks");
users.forEach(elem -> System.out.println(elem));
Here, our lambda expression is only calling the System.out.println
method.
Therefore, we could rewrite it like this:
List<String> users = Arrays.asList("java","8","rocks");
users.forEach(System.out::println);
Conclusion
Lambdas are one of the main additions to Java 8. And while you can still write code the way you used to do it before (using anonymous classes) chances are that you will start to see more and more lambdas going around other people code. So you should at least know they exist and how they can be used effectively.
Functional interfaces are not a small addition to the language but the fact that you can use a lambda expression or method reference every time you expect an interface is a huge deal. Is not only the fact that you remove a lot of boilerplate code but also that by doing that you are actually making your code easier to read and maintain. Having this concept applied to a lot of the existing language interfaces will also help a lot.
Take advantage of the interfaces defined for you in java.util.function
. They
are abstractions that come up quite frequently in practice and are very powerful
given the way you can combine them. If you need to use them for primitive types
like int or double remember that you have the option to use primitive
specializations of these interfaces to avoid the performance cost of autoboxing.
JavaJava 8Functional ProgrammingLambdasFunctional interfacesMethod reference
2088 Words
2014-11-18 00:00 +0000