With the release of Java's version 8 back in 2014, it has quickly become the most quickly adopted Java version to date.
Why are people so excited about it?
Well there are some pretty neat new “toys” we can play around with.
One such “toy” are lambda expressions. Though they may seem complex, I'll try my best to break them down and make them as simple as possible.
Benefits of the Lambda Expression
From what I can tell, there's really only one big advantage to using a Lambda expression…
Less code to write!
I'll explain what I mean with an example.
Let's consider the compare()
method, which is used as part of the Comparator
interface.
We typically see the Comparator
used as an anonymous inner class, and anonymous inner classes are notorious for a lot of “noise”.
Let's assume we have a list of Transaction
s that need to be sorted by their date. How would we accomplish this without Lambas?
List<Transaction> transactions = getTransactionsFromDB(); Collections.sort(transactions, new Comparator<Transaction>() { @Override public int compare(Transaction t1, Transaction t2) { return t1.getDate().compareTo(t2.getDate()); } });
Now let's take a look at how we can make this less verbose with Lambdas:
List<Transaction> transactions = getTransactionsFromDB(); Collections.sort(transactions, (Transaction t1, Transaction t2) -> t1.getDate().compareTo(t2.getDate()));
A lot less code right?
Much less… but with less code comes a bit less clarity sometimes as to what's actually going on. So let's talk syntax.
The Lambda Syntax
The lambda is represented by the arrow ->
syntax.
Arguments go on the left of the arrow and the body of the expression goes on the right.
In our example above, the arguments are Transaction t1, Transaction t2
.
The body of the expression is t1.getDate().compareTo(t2.getDate())
.
But how does Java know what to do given that there seems to be a lack of information for it to execute on?
Here's how Java works its magic… When you're executing the Collections.sort()
method, the second argument that it expects is a Comparator
.
The Comparator
is an interface that defines one method that takes two templated parameters (in our case the templated parameters are Transaction t1
and Transaction t2
)
So, this means that since Java knows that you should inserting a Comparator as the second parameter of the Collections.sort()
method, it has some sort of idea as to what method you could be implementing.
Then when you insert our two Transaction
arguments to the left of the Lambda syntax, Java knows that you must want to use the compare
method.
I think of it like method overloading. When you have a bunch of methods that all share the exact same name, but they all have different method signatures… Java knows which method to invoke based on the arguments you pass in to the method. The same concept is happening here, it's using the method signatures to figure out which of the interface's methods you'd like to invoke.
Then, since this is an interface after all, there's no body in the interface's method… so we need to give it some code! This is the code that we include to the right of the Lambda's arrow syntax.
Java can then put all the pieces together and re-create the more verbose anonymous inner class code and your program will work just as well as it did before.
Further Shortening of Lambda Code
One more thing that you can do to shorten your Lambda expression code is to remove the parameter types for the parameters to the left of the arrow syntax.
Java will be able to infer the data types in most cases, so they can almost always be omitted.
Let's revisit our sorting code from before and remove the data types:
List<Transaction> transactions = getTransactionsFromDB(); Collections.sort(transactions, (t1, t2) -> t1.getDate().compareTo(t2.getDate()));
Do you see how it now just says (t1, t2)
instead of (Transaction t1, Transaction t2)
?
That's more Java 8 magic at work. Java knows that the (t1, t2)
variables should be of type Transaction
.
Pretty cool stuff.
Let's look at more examples!
Looping with Lambdas
Java 8 also released a handy method called forEach
for Collections.
So now we can just execute a .forEach()
method on collections that have iterators.
Let's use lambdas in combination with this forEach()
method to output our Transaction
s to the console.
List<Transaction> transactions = getTransactionsFromDB(); transactions.forEach(t -> System.out.println(t));
So this begs the question… what abstract method is the forEach
method working off of?
Well, the forEach
method takes a Consumer<T> action
as a parameter.
The Consumer
interface has a method that takes a generic T
object as a parameter.
public interface Consumer<T> { /** * Performs this operation on the given argument. * * @param t the input argument */ void accept(T t); }
To help make things more clear, let's re-write this with an anonymous inner class:
List<Transaction> transactions = getTransactionsFromDB(); transactions.forEach(new Consumer<Transaction>() { @Override public void accept(Transaction t) { System.out.println(t); } });
So again, the forEach
method takes a Consumer
as a paramter. The Consumer
interface defines a method called accept
, which takes a generic object (in our case, it's taking a Transaction
). We then define a body for the accept
method, which outputs a transaction
to the console.
Neat right?
I'm sure with time, as Lambdas become more widely accepted and used, they will become second nature to us Java programmers.
So for now, when you're writing your code and you're about to create an anonymous inner class, pause for a second and think of how you could turn it into a lambda expression instead!