Skip to content
  • Lambdas & Method References · Java

Lambdas & Method References

LearnViaNotes @learnvianotes· 4 min read

A lambda is an anonymous function you can pass around as a value; a method reference is a lambda that just calls an existing method.

Syntax forms

FormExample
No parameters() -> 42
One parameter (parentheses optional)s -> s.length()
Several parameters(a, b) -> a + b
Explicit types(String a, String b) -> a.compareTo(b)
var parameters (11+, allows annotations)(var a, var b) -> a + b
Block body (needs return)x -> { int y = x * 2; return y + 1; }
Java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(List.of("Ravi", "Asha", "Zoe", "Mohan"));

        // Before Java 8: an anonymous class
        names.sort(new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                return a.compareTo(b);
            }
        });
        System.out.println(names);

        // Java 8+: a lambda
        names.sort((a, b) -> b.length() - a.length());
        System.out.println(names);

        names.removeIf(s -> s.length() < 4);
        names.forEach(s -> System.out.print(s + " "));
        System.out.println();
    }
}
Output
[Asha, Mohan, Ravi, Zoe]
[Mohan, Asha, Ravi, Zoe]
Mohan Asha Ravi

Capturing variables: effectively final

A lambda can read local variables from the enclosing method only if they are effectively final — never reassigned after initialisation. The lambda captures the value, and Java forbids the variable changing underneath it.

Java
public class Main {
    public static void main(String[] args) {
        int count = 0;
        Runnable r = () -> count++;     // error: must be final or effectively final
        r.run();
    }
}
Java
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

public class Main {
    public static void main(String[] args) {
        String prefix = "item-";                  // effectively final: OK to capture
        AtomicInteger counter = new AtomicInteger();  // the reference is final; its state can change
        List.of("a", "b", "c").forEach(s -> {
            counter.incrementAndGet();
            System.out.println(prefix + s);
        });
        System.out.println("count = " + counter.get());
    }
}
Output
item-a
item-b
item-c
count = 3

this in a lambda vs an anonymous class

Java
public class Main {
    String name = "outer";

    void demo() {
        Runnable lambda = () -> System.out.println("lambda this: " + this.name);
        Runnable anon = new Runnable() {
            String name = "anonymous";
            @Override
            public void run() {
                System.out.println("anon this:   " + this.name);
            }
        };
        lambda.run();
        anon.run();
    }

    public static void main(String[] args) {
        new Main().demo();
    }
}
Output
lambda this: outer
anon this:   anonymous
LambdaAnonymous class
this refers tothe enclosing instancethe anonymous object itself
New scope?No — can't redeclare an enclosing local nameYes — can shadow names
Implementsonly functional interfacesany interface or class, many methods, can hold state
Compiled toinvokedynamic + a private method (no extra .class file)a separate Outer$1.class

Method references — four kinds

When a lambda only forwards its arguments to one method, write Class::method instead.

KindSyntaxEquivalent lambda
Static methodInteger::parseInts -> Integer.parseInt(s)
Instance method of a particular objectSystem.out::printlnx -> System.out.println(x)
Instance method of an arbitrary object of a typeString::toUpperCases -> s.toUpperCase()
ConstructorArrayList::new() -> new ArrayList<>()
Java
import java.util.*;
import java.util.function.*;

public class Main {
    public static void main(String[] args) {
        Function<String, Integer> parse = Integer::parseInt;         // static
        String greeting = "Hello";
        Predicate<String> startsWithIt = greeting::startsWith;       // particular object
        Function<String, String> upper = String::toUpperCase;        // arbitrary object
        BiFunction<String, String, Boolean> eq = String::equalsIgnoreCase; // first arg is the receiver
        Supplier<List<String>> newList = ArrayList::new;             // constructor
        Function<Integer, int[]> newArray = int[]::new;              // array constructor

        System.out.println(parse.apply("41") + 1);
        System.out.println(startsWithIt.test("Hell"));
        System.out.println(upper.apply("java"));
        System.out.println(eq.apply("JAVA", "java"));
        List<String> list = newList.get();
        list.add("x");
        System.out.println(list + " " + newArray.apply(3).length);
    }
}
Output
42
true
JAVA
true
[x] 3

Are lambdas just syntactic sugar for anonymous classes?

Why must captured local variables be effectively final?

What does String::compareTo mean when a Comparator<String> is expected?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report