Who is this article for?
The material does not claim to have any academic value, let alone novelty. Quite the contrary: I will try to describe things that are complex (for some people) as simply as possible.
A request to explain the Stream API inspired me to write this. I thought about it and decided that some of my stream examples would be incomprehensible without an understanding of lambda expressions. So we'll start with lambda expressions.
- It's for people who read the first part of this article;
- It's for people who think they already know Java Core well, but have no clue about lambda expressions in Java. Or maybe they've heard something about lambda expressions, but the details are lacking.
- It's for people who have a certain understanding of lambda expressions, but are still daunted by them and unaccustomed to using them.

Access to external variables
Does this code compile with an anonymous class?
int counter = 0;
Runnable r = new Runnable() {
@Override
public void run() {
counter++;
}
};
No. The counter
variable must be final
. Or if not final
, then at least it cannot change its value.
The same principle applies in lambda expressions. They can access all the variables that they can "see" from the place they are declared. But a lambda must not change them (assign a new value to them).
However, there is a way to bypass this restriction in anonymous classes. Simply create a reference variable and change the object's internal state. In doing so, the variable itself does not change (points to the same object) and can be safely marked as final
.
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() {
@Override
public void run() {
counter.incrementAndGet();
}
};
Here our counter
variable is a reference to an AtomicInteger
object. And the incrementAndGet()
method is used to change the state of this object. The value of the variable itself does not change while the program is running. It always points to the same object, which lets us declare the variable with the final keyword.
Here are the same examples, but with lambda expressions:
int counter = 0;
Runnable r = () -> counter++;
This won't compile for the same reason as the version with an anonymous class: counter
must not change while the program is running.
But everything is fine if we do it like this:
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = () -> counter.incrementAndGet();
This also applies to calling methods. Within lambda expressions, you can not only access all "visible" variables, but also call any accessible methods.
public class Main {
public static void main(String[] args) {
Runnable runnable = () -> staticMethod();
new Thread(runnable).start();
}
private static void staticMethod() {
System.out.println("I'm staticMethod(), and someone just called me!");
}
}
Although staticMethod()
is private, it is accessible inside the main()
method, so it can also be called from inside a lambda created in the main
method.
When is a lambda expression executed?
You may find the following question too simple, but you should ask it just the same: when will the code inside the lambda expression be executed? When it is created? Or when it is called (which is not yet known)? This is fairly easy to check.
System.out.println("Program start");
// All sorts of code here
// ...
System.out.println("Before lambda declaration");
Runnable runnable = () -> System.out.println("I'm a lambda!");
System.out.println("After lambda declaration");
// All sorts of other code here
// ...
System.out.println("Before passing the lambda to the thread");
new Thread(runnable).start();
Screen output:
Program start
Before lambda declaration
After lambda declaration
Before passing the lambda to the thread
I'm a lambda!
You can see that the lambda expression was executed at the very end, after the thread was created and only when the program's execution reaches the run()
method. Certainly not when it is declared. By declaring a lambda expression, we've only created a Runnable
object and described how its run()
method behaves. The method itself is executed much later.
Method references?
Method references aren't directly related to lambdas, but I think it makes sense to say a few words about them in this article. Suppose we have a lambda expression that doesn't do anything special, but simply calls a method.
x -> System.out.println(x)
It receives some x
and just calls System.out.println()
, passing in x
. In this case, we can replace it with a reference to the desired method. Like this:
System.out::println
That's right — no parentheses at the end! Here's a more complete example:
List<String> strings = new LinkedList<>();
strings.add("Dota");
strings.add("GTA5");
strings.add("Halo");
strings.forEach(x -> System.out.println(x));
In the last line, we use the forEach()
method, which takes an object that implements the Consumer
interface. Again, this is a functional interface, having just one void accept(T t)
method. Accordingly, we write a lambda expression that has one parameter (because it is typed in the interface itself, we don't specify the parameter type; we only indicate that we will call it x
). In the body of the lambda expression, we write the code that will be executed when the accept()
method is called.
Here we simply display what ended up in the x
variable. This same forEach()
method iterates through all the elements in the collection and calls the accept()
method on the implementation of the Consumer
interface (our lambda), passing in each item in the collection.
As I said, we can replace such a lambda expression (one that simply class a different method) with a reference to the desired method. Then our code will look like this:
List<String> strings = new LinkedList<>();
strings.add("Dota");
strings.add("GTA5");
strings.add("Halo");
strings.forEach(System.out::println);
The main thing is that the parameters of the println()
and accept()
methods match. Because the println()
method can accept anything (it is overloaded for all primitives types and all objects), instead of lambda expressions, we can simply pass a reference to the println()
method to forEach()
. Then forEach()
will take each element in the collection and pass it directly to the println()
method.
For anyone encountering this for the first time, please note that we are not calling System.out.println()
(with dots between words and with parentheses at the end). Instead, we are passing a reference to this method.
If we write this
strings.forEach(System.out.println());
we will have a compilation error. Before the call to forEach()
, Java sees that System.out.println()
is being called, so it understands that the return value is void
and will try to pass void
to forEach()
, which is instead expecting a Consumer
object.
Syntax for method references
It's quite simple:We pass a reference to a static method like this:
ClassName::staticMethodName
public class Main { public static void main(String[] args) { List<String> strings = new LinkedList<>(); strings.add("Dota"); strings.add("GTA5"); strings.add("Halo"); strings.forEach(Main::staticMethod); } private static void staticMethod(String s) { // Do something } }
We pass a reference to a non-static method using an existing object, like this:
objectName::instanceMethodName
public class Main { public static void main(String[] args) { List<String> strings = new LinkedList<>(); strings.add("Dota"); strings.add("GTA5"); strings.add("Halo"); Main instance = new Main(); strings.forEach(instance::nonStaticMethod); } private void nonStaticMethod(String s) { // Do something } }
We pass a reference to a non-static method using the class that implements it as follows:
ClassName::methodName
public class Main { public static void main(String[] args) { List<User> users = new LinkedList<>(); users.add (new User("John")); users.add(new User("Paul")); users.add(new User("George")); users.forEach(User::print); } private static class User { private String name; private User(String name) { this.name = name; } private void print() { System.out.println(name); } } }
We pass a reference to a constructor like this:
ClassName::new
Method references are very convenient when you already have a method that would work perfectly as a callback. In this case, instead of writing a lambda expression containing the method's code, or writing a lambda expression that simply calls the method, we simply pass a reference to it. And that's it.
An interesting distinction between anonymous classes and lambda expressions
In an anonymous class, thethis
keyword points to an object of the anonymous class. But if we use this inside a lambda, we gain access to the object of the containing class. The one where we actually wrote the lambda expression. This happens because lambda expressions are compiled into a private method of the class they are written in.
I would not recommend using this "feature", since it has a side effect and that contradicts the principles of functional programming. That said, this approach is entirely consistent with OOP. ;)
Where did I get my information and what else should you read?
- Tutorial on Oracle's official website. A lot of detailed information, including examples.
- Chapter about method references in the same Oracle tutorial.
- Get sucked into Wikipedia if you're truly curious.
GO TO FULL VERSION