Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Java  Module 9 – Lambdas, functional interfaces and Optional

Java lambda expressions and target typing

Write Java lambda expressions, see target typing give one lambda four types, fix effectively final errors and read what javac compiles a lambda into.

  • Intermediate
  • 20 minutes
  • Examples run with Java 25.0.4 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Write lambda expressions for functional interfaces
  • Explain target typing and effectively final capture
  • Choose between a lambda, a method reference and a named method

A lambda expression is a function written right where it is used: m -> m >= 40 takes a mark and says whether it passes. In Java a lambda is never a standalone function, though. It is an object that implements a functional interface, an interface with exactly one abstract method, such as Predicate<Integer> or Comparator<Student>. The lambda never names that interface. The compiler takes it from the place where the lambda appears, its target type, so the same text m -> m >= 40 is an IntPredicate on one line and a Function<Integer, Boolean> on the next. Two more rules complete the picture: a lambda may use the local variables of the method around it only if they never change after they get their value (they are effectively final), and this inside a lambda means the object of the code around it. Every rule below is run on JDK 25, compiler errors included, and the last section uses javap to show what javac turns a lambda into.

From an anonymous class to a lambda

Before Java 8, handing a piece of behaviour to a method meant writing a class. Sorting students by marks, highest first, needed an anonymous class that implements Comparator<Student>. The lambda says the same thing in one line:

SortNames.java Java · SortNames.java
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

record Student(String name, int marks) {
    @Override
    public String toString() { return name + " " + marks; }
}

class SortNames {
    static void main() {
        List<Student> students = List.of(new Student("Ravi", 82), new Student("Asha", 91),
                new Student("Meera", 82), new Student("Kabir", 67));

        // Before Java 8: an anonymous class that implements Comparator.
        List<Student> first = new ArrayList<>(students);
        first.sort(new Comparator<Student>() {
            @Override
            public int compare(Student a, Student b) {
                return Integer.compare(b.marks(), a.marks());
            }
        });
        IO.println("anonymous class: " + first);

        // The same comparator as a lambda expression.
        List<Student> second = new ArrayList<>(students);
        second.sort((a, b) -> Integer.compare(b.marks(), a.marks()));
        IO.println("lambda:          " + second);
    }
}
Both versions sort alike; only one adds a class file Python · sort_session.py
from session import run

run("""
java SortNames.java
javac -d classes SortNames.java
ls classes
""")

Output

# javac 25.0.4.1
$ java SortNames.java
anonymous class: [Asha 91, Ravi 82, Meera 82, Kabir 67]
lambda:          [Asha 91, Ravi 82, Meera 82, Kabir 67]
$ javac -d classes SortNames.java
$ ls classes
SortNames.class
SortNames$1.class
Student.class

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 sort_session.py

Both sorts give the same order. Ravi stays ahead of Meera on 82 marks because List.sort is stable: elements that compare as equal keep their input order, which the comparators lesson of this module builds on. The listing of the compiled classes shows a difference you cannot see in the output: the anonymous class became a file of its own, SortNames$1.class, while the lambda added no class file at all. Where its code went is the subject of the last section.

Read the lambda from left to right. (a, b) are its parameters, -> separates them from the body, and Integer.compare(b.marks(), a.marks()) is the body, an expression whose value is the result. Nothing says that a and b are Student objects or that the result is an int: both come from the one abstract method of the target type, int compare(Student a, Student b).

The syntax, piece by piece

LambdaForms.java Java · LambdaForms.java
import java.util.Map;
import java.util.TreeMap;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.BinaryOperator;
import java.util.function.Function;

class LambdaForms {
    static void main() {
        Runnable hello = () -> IO.println("no parameters");
        Function<String, Integer> length = s -> s.length();                 // one parameter
        BinaryOperator<Integer> add = (a, b) -> a + b;                      // two parameters
        BiFunction<String, Integer, String> repeat = (String s, Integer n) -> s.repeat(n);
        BiPredicate<String, String> same = (var x, var y) -> x.equalsIgnoreCase(y);
        Function<Integer, String> grade = marks -> {                        // a block body
            if (marks >= 90) return "A";
            if (marks >= 75) return "B";
            return "C";
        };

        hello.run();
        IO.println(length.apply("target"));
        IO.println(add.apply(40, 2));
        IO.println(repeat.apply("ab", 3));
        IO.println(same.test("Java", "JAVA"));
        IO.println(grade.apply(82));

        Map<String, Integer> marks = new TreeMap<>(Map.of("Asha", 91, "Ravi", 82));
        marks.forEach((_, m) -> IO.println("a mark: " + m));                 // _ : a parameter not used
    }
}
Every form of the syntax, run Python · forms_session.py
from session import run

run("""
java LambdaForms.java
""")

Output

# javac 25.0.4.1
$ java LambdaForms.java
no parameters
6
42
ababab
true
B
a mark: 91
a mark: 82

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 forms_session.py

The program uses every form of the syntax once:

  • No parameters: () -> IO.println("no parameters"), the shape of Runnable and Supplier.
  • One parameter: s -> s.length(). Only here may the parentheses be left out.
  • Several parameters: (a, b) -> a + b, with the types inferred from the target.
  • Declared types: (String s, Integer n) -> s.repeat(n). Type all the parameters or none of them.
  • var (Java 11): (var x, var y) -> x.equalsIgnoreCase(y), useful when a parameter needs an annotation.
  • A block body: marks -> { if (…) return "A"; … }, for several statements; every value needs a return.
  • Unnamed _ (Java 22): (_, m) -> IO.println("a mark: " + m), for a parameter the body does not use.

An expression body has no return and no semicolon: its value is the result. A block body is an ordinary method body, so a lambda whose target method returns a value must return it on every path, and one whose target returns void must not return a value. The forms cannot be mixed in one parameter list: (var x, y) and (String x, y) are both compile errors. When a block body grows past a few lines, as grade already has, it reads better as a named method, a choice the section on lambdas, method references and named methods comes back to.

Target typing: the context decides the type

A lambda has no type of its own. The Java Language Specification (§15.27) allows one only where the context supplies a type: an assignment or variable declaration (a return statement counts, since it assigns the method’s result), an argument of a method call, or a cast. The program below writes the same lambda four times and prints which interface each resulting object implements:

TargetTyping.java Java · TargetTyping.java
import java.util.function.Function;
import java.util.function.IntPredicate;
import java.util.function.Predicate;

interface MarksRule {
    boolean passes(int marks);
}

class TargetTyping {
    static String typeOf(Object lambda) {
        return lambda.getClass().getInterfaces()[0].getSimpleName();
    }

    static IntPredicate atLeast(int min) {
        return m -> m >= min;                          // return context
    }

    static void report(String what, Predicate<String> rule) {
        IO.println(what + " -> " + rule.test("   "));
    }

    static void main() {
        // One lambda text, four target types (assignment contexts).
        IntPredicate a = m -> m >= 40;
        Predicate<Integer> b = m -> m >= 40;
        Function<Integer, Boolean> c = m -> m >= 40;
        MarksRule d = m -> m >= 40;
        IO.println(typeOf(a) + ", " + typeOf(b) + ", " + typeOf(c) + ", " + typeOf(d));
        IO.println(a.test(38) + " " + b.test(38) + " " + c.apply(38) + " " + d.passes(38));

        report("blank?", s -> s.isBlank());            // invocation context
        IO.println(atLeast(35).test(38));
        Object task = (Runnable) () -> IO.println("cast context");
        ((Runnable) task).run();
    }
}
The lambda m -> m >= 40 becomes an IntPredicate, a Predicate<Integer>, a Function<Integer, Boolean> or a MarksRule, as its context requires.One lambda expressionm -> m >= 40The context supplies the target typeAt run time: an object of that interface;calling its one method runs the bodyIntPredicate a = …boolean test(int m)Predicate<Integer> b = …boolean test(Integer m)Function<Integer, Boolean> c = …Boolean apply(Integer m)MarksRule d = … (your own interface)boolean passes(int m)javac checks the body againstthe interface's one abstract methodevaluating the lambda gives

One lambda expression, four target types

Text description of the diagram

The diagram shows how the same lambda text gets its type from where it is written.

  1. At the top is one lambda expression: m -> m >= 40. It does not name any interface.
  2. In the middle are four declarations that each give it a target type. IntPredicate a, whose one abstract method is boolean test(int m). Predicate<Integer> b, whose method is boolean test(Integer m). Function<Integer, Boolean> c, whose method is Boolean apply(Integer m). MarksRule d, an interface written for the lesson, whose method is boolean passes(int m).
  3. The arrow from the lambda to the declarations says that javac checks the body against the one abstract method of the target interface: the parameter m is an int for IntPredicate and MarksRule and an Integer for the other two, and the result must fit the method's return type.
  4. At the bottom, evaluating the lambda produces an object of that interface. Calling its one method runs the lambda's body.
Four target types, then two lambdas without one Python · target_session.py
from session import run

run("""
java TargetTyping.java
java NoTarget.java
""", fails=("java NoTarget.java",))

Output

# javac 25.0.4.1
$ java TargetTyping.java
IntPredicate, Predicate, Function, MarksRule
false false false false
blank? -> true
true
cast context
$ java NoTarget.java
NoTarget.java:3: error: cannot infer type for local variable rule
        var rule = m -> m >= 40;
            ^
  (lambda expression needs an explicit target-type)
NoTarget.java:4: error: incompatible types: Object is not a functional interface
        Object task = () -> IO.println("hello");
                      ^
2 errors
error: compilation failed
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 target_session.py

The first line of output is the point of the section: one lambda text, four interfaces. The target also fixes the types inside the lambda. For IntPredicate and MarksRule the parameter m is an int; for Predicate<Integer> and Function<Integer, Boolean> it is an Integer, unboxed for the comparison. report(…) shows the most common context of all, a method argument, and atLeast(35) a return statement.

NoTarget.java fails for two different reasons. var takes a variable’s type from its initial value, and a lambda has none to give, so javac asks for an explicit target type. Object is not a functional interface, because it has no abstract method for the lambda to implement. Both are fixed by naming an interface: declare IntPredicate rule = …, or cast as TargetTyping does with (Runnable) () -> ….

Capturing variables: the effectively final rule

A lambda may read local variables of the method it is written in. That is called capturing them, and there is a limit. Here a lambda tries to count the failed marks:

CountFailures.java Java · CountFailures.java
import java.util.List;

class CountFailures {
    static void main() {
        List<Integer> marks = List.of(82, 38, 91, 25, 67);
        int failed = 0;
        marks.forEach(m -> {
            if (m < 40) failed++;
        });
        IO.println(failed + " failed");
    }
}
CountFixed.java Java · CountFixed.java
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

class CountFixed {
    static void main() {
        List<Integer> marks = List.of(82, 38, 91, 25, 67);

        // 1. A plain loop: no lambda, so the local variable may change.
        int failed = 0;
        for (int m : marks) {
            if (m < 40) failed++;
        }
        IO.println("loop:          " + failed + " failed");

        // 2. Let the stream count, so there is nothing to change.
        long count = marks.stream().filter(m -> m < 40).count();
        IO.println("stream:        " + count + " failed");

        // 3. The variable never changes; the object it points to does.
        AtomicInteger counter = new AtomicInteger();
        marks.forEach(m -> {
            if (m < 40) counter.incrementAndGet();
        });
        IO.println("AtomicInteger: " + counter.get() + " failed");

        // Effectively final: assigned once and never changed, so a lambda may read it.
        int passMark = 40;
        IO.println("passed:        " + marks.stream().filter(m -> m >= passMark).count());
    }
}
The compile error, then three ways that work Python · capture_session.py
from session import run

run("""
java CountFailures.java
java CountFixed.java
""", fails=("java CountFailures.java",))

Output

# javac 25.0.4.1
$ java CountFailures.java
CountFailures.java:8: error: local variables referenced from a lambda expression must be final or effectively final
            if (m < 40) failed++;
                        ^
1 error
error: compilation failed
[exit status 1]
$ java CountFixed.java
loop:          2 failed
stream:        2 failed
AtomicInteger: 2 failed
passed:        3

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 capture_session.py

The specification requires every local variable a lambda uses but does not declare to be final or effectively final (§15.27.2). A variable is effectively final when it is never assigned again after it gets its value and is never incremented or decremented (§4.12.4), so that adding final would change nothing. failed++ breaks that. The rule protects you: a lambda captures the value of the variable and may run later, after the method has returned, or on another thread. If the variable could still change, the lambda’s copy would silently disagree with it, and changes from several threads would race. The specification names that risk as its reason for the rule.

CountFixed.java shows three ways out:

  1. A plain loop. No lambda, so nothing is captured and the local may change freely.
  2. Ask the stream for the count. filter(…).count() leaves nothing to change at all. This is usually the clearest choice.
  3. A mutable object. counter itself never changes; the AtomicInteger it points to does. AtomicInteger also stays correct if the stream later runs in parallel. A one-element array, int[] failed = {0}, compiles for the same reason but is not safe across threads.

The last line reads passMark inside a lambda, which is fine because it is assigned once. Write passMark = 35; anywhere else in the method, even after the lambda, and the lambda stops compiling: the rule is about the variable, not about the order of the statements. Fields are different: a lambda reaches an instance field through this, which never changes, and a static field through its class, so it may update fields, with the same thread-safety questions as any other code.

What this means inside a lambda

An anonymous class is a class, so inside it this is the anonymous object. A lambda is not a new scope for names: inside its body, this, super and every name other than its own parameters mean exactly what they mean in the code around it (§15.27.2).

ThisDemo.java Java · ThisDemo.java
class Report {
    private final String title = "Monthly report";

    void show() {
        Runnable anonymous = new Runnable() {
            @Override
            public void run() {
                IO.println("anonymous class: this is a " + this.getClass().getName());
                IO.println("                 the title is " + Report.this.title);
            }
        };
        Runnable lambda = () -> {
            IO.println("lambda:          this is a " + this.getClass().getName());
            IO.println("                 the title is " + this.title);
        };
        anonymous.run();
        lambda.run();
    }
}

class ThisDemo {
    static void main() {
        new Report().show();
    }
}
Shadow.java Java · Shadow.java
import java.util.List;

class Shadow {
    static void main() {
        String name = "Asha";
        List.of("Ravi", "Meera").forEach(name -> IO.println(name));
    }
}
this in an anonymous class and in a lambda; a parameter that clashes Python · this_session.py
from session import run

run("""
java ThisDemo.java
java Shadow.java
""", fails=("java Shadow.java",))

Output

# javac 25.0.4.1
$ java ThisDemo.java
anonymous class: this is a Report$1
                 the title is Monthly report
lambda:          this is a Report
                 the title is Monthly report
$ java Shadow.java
Shadow.java:6: error: variable name is already defined in method main()
        List.of("Ravi", "Meera").forEach(name -> IO.println(name));
                                         ^
1 error
error: compilation failed
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 this_session.py

In the anonymous class, this.title would not even compile, because there this is the Runnable, so it reaches the report as Report.this.title (or as plain title). In the lambda, this.title is the report’s title. The same rule explains the error in Shadow.java: because the lambda shares the scope of main, its parameter may not reuse the name of the local variable name. Inside a lambda you also cannot refer to the lambda itself through this. When an object needs its own state or several methods, write a class.

What javac makes of a lambda

The anonymous class left a .class file behind. To see where a lambda’s code goes, compile a small class and disassemble it with javap, the class-file disassembler that comes with the JDK:

Rules.java Java · Rules.java
import java.util.function.IntPredicate;

class Rules {
    static IntPredicate passMark() {
        return m -> m >= 40;
    }

    static IntPredicate atLeast(int min) {
        return m -> m >= min;
    }
}
SameObject.java Java · SameObject.java
class SameObject {
    static void main() {
        IO.println(Rules.passMark().test(82));
        IO.println("hidden class: " + Rules.passMark().getClass().isHidden());
        // Each call evaluates the lambda expression again: one object, or a new one each time?
        IO.println("no capture, same object: " + (Rules.passMark() == Rules.passMark()));
        IO.println("capture, same object:    " + (Rules.atLeast(40) == Rules.atLeast(40)));
    }
}
javap -c -p: an invokedynamic instead of a class Python · bytecode_session.py
from session import run

run("""
javac -d classes Rules.java SameObject.java
ls classes
javap -c -p classes/Rules.class
javap -v classes/Rules.class | grep -A 1 '^BootstrapMethods:'
java -cp classes SameObject
""")

Output

# javac 25.0.4.1
$ javac -d classes Rules.java SameObject.java
$ ls classes
Rules.class
SameObject.class
$ javap -c -p classes/Rules.class
Compiled from "Rules.java"
class Rules {
  Rules();
    Code:
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return

  static java.util.function.IntPredicate passMark();
    Code:
         0: invokedynamic #7,  0              // InvokeDynamic #0:test:()Ljava/util/function/IntPredicate;
         5: areturn

  static java.util.function.IntPredicate atLeast(int);
    Code:
         0: iload_0
         1: invokedynamic #11,  0             // InvokeDynamic #1:test:(I)Ljava/util/function/IntPredicate;
         6: areturn

  private static boolean lambda$atLeast$0(int, int);
    Code:
         0: iload_1
         1: iload_0
         2: if_icmplt     9
         5: iconst_1
         6: goto          10
         9: iconst_0
        10: ireturn

  private static boolean lambda$passMark$0(int);
    Code:
         0: iload_0
         1: bipush        40
         3: if_icmplt     10
         6: iconst_1
         7: goto          11
        10: iconst_0
        11: ireturn
}
$ javap -v classes/Rules.class | grep -A 1 '^BootstrapMethods:'
BootstrapMethods:
  0: #35 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
$ java -cp classes SameObject
true
hidden class: true
no capture, same object: true
capture, same object:    false

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 bytecode_session.py

  • The body became a private static method of Rules: lambda$passMark$0(int). The capturing lambda’s method, lambda$atLeast$0(int, int), has one more parameter, the captured min, placed before the lambda’s own parameter. That is what capturing means at the bytecode level: a value passed along when the object is made.
  • The method that evaluates the lambda does it with one invokedynamic instruction, not a new. Its bootstrap method, listed under BootstrapMethods, is LambdaMetafactory.metafactory. The first time that instruction runs, the JVM calls the bootstrap method, which creates a class implementing IntPredicate whose test method calls the private method. The class is a hidden class, made in memory, which is why no file appeared on disk.
  • The last lines show something you must not rely on. On JDK 25, evaluating the lambda without captures twice gave the same object, and the capturing one gave a new object each time. The specification allows either (§15.27.4), so never compare lambdas with ==, lock on them or use their identity hash codes.

Lambda, method reference or named method?

The three are interchangeable for the compiler, so the choice is about the reader:

  • A lambda when the behaviour is short, used once and best read where it is used, often with a captured value: m -> m >= passMark.
  • A method reference when an existing method already does exactly the job: Student::marks, Integer::parseInt.
  • A named method when the body needs several lines, a name to explain it, a test of its own or more than one caller: static String grade(int marks), passed as Grades::grade.

A useful test: if you have to read the body of a lambda twice to know what it does, give it a name. The grade lambda in LambdaForms.java already fails that test, and as a method it could be tested on its own. Method references, the short form for “call this existing method”, are the third lesson of this module.

Your turn: applyAll and a filter pipeline

Write two methods, then check them against the cases in main:

  1. applyAll(List<Integer> xs, List<IntUnaryOperator> ops) returns a new list in which each number has had every operator applied, in the order of the list. With no operators it returns the numbers unchanged.
  2. keepAll(List<String> words, List<Predicate<String>> rules) keeps the words that pass every rule. The rules run in order, and a word’s checks stop at the first rule it fails.

The second case matters more than it looks: one rule calls w.charAt(0), which throws for the empty string, so the rule that rejects empty words has to run first and stop the checks. Write your version before you read the one below.

ApplyAll.java: one solution, with its checks Java · ApplyAll.java
import java.util.ArrayList;
import java.util.List;
import java.util.function.IntUnaryOperator;
import java.util.function.Predicate;

class ApplyAll {
    /** Applies every operator, in list order, to each number. */
    static List<Integer> applyAll(List<Integer> xs, List<IntUnaryOperator> ops) {
        List<Integer> out = new ArrayList<>();
        for (int x : xs) {
            int value = x;
            for (IntUnaryOperator op : ops) value = op.applyAsInt(value);
            out.add(value);
        }
        return out;
    }

    /** Keeps the words that pass every rule; rules run in order and stop at the first failure. */
    static List<String> keepAll(List<String> words, List<Predicate<String>> rules) {
        List<String> out = new ArrayList<>();
        for (String w : words) {
            boolean keep = true;
            for (Predicate<String> rule : rules) {
                if (!rule.test(w)) {
                    keep = false;
                    break;
                }
            }
            if (keep) out.add(w);
        }
        return out;
    }

    static void check(String what, Object got, Object want) {
        IO.println((got.equals(want) ? "ok    " : "WRONG ") + what + ": " + got);
    }

    static void main() {
        List<IntUnaryOperator> ops = List.of(x -> x + 1, x -> x * 10);
        check("add 1, then times 10", applyAll(List.of(1, 2, 3), ops), List.of(20, 30, 40));
        check("the other order", applyAll(List.of(1, 2, 3), List.of(x -> x * 10, x -> x + 1)), List.of(11, 21, 31));
        check("no operators", applyAll(List.of(5, 6), List.of()), List.of(5, 6));
        check("no numbers", applyAll(List.of(), ops), List.of());

        List<Predicate<String>> rules = List.of(
                w -> !w.isEmpty(),                         // must run first: charAt(0) needs a character
                w -> Character.isUpperCase(w.charAt(0)),
                w -> w.length() <= 5);
        check("three rules", keepAll(List.of("Asha", "ravi", "", "Meenakshi", "Kabir"), rules), List.of("Asha", "Kabir"));
        check("no rules", keepAll(List.of("a", "b"), List.of()), List.of("a", "b"));
    }
}
The checks Python · exercise_session.py
from session import run

run("""
java ApplyAll.java
""")

Output

# javac 25.0.4.1
$ java ApplyAll.java
ok    add 1, then times 10: [20, 30, 40]
ok    the other order: [11, 21, 31]
ok    no operators: [5, 6]
ok    no numbers: []
ok    three rules: [Asha, Kabir]
ok    no rules: [a, b]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 exercise_session.py

The early break in keepAll is what keeps the empty string away from charAt(0). Predicate.and, which the next lesson uses to join rules, stops at the first failing rule in the same way.

Interview questions

Fresher · What does effectively final mean, and why must local variables used in a lambda be effectively final?

A local variable is effectively final when it is never assigned again after it gets its value and is never incremented or decremented, so adding final would change nothing. A lambda that uses such a variable captures its value, and the lambda may run later, after the method has returned, or on another thread. If the variable could still change, the copy inside the lambda and the variable could disagree, and updates from several threads would race, so the Java Language Specification forbids it. javac reports that local variables referenced from a lambda expression must be final or effectively final. To count or collect, let the stream do it with count() or collect(), use a plain loop, or keep a mutable object such as an AtomicInteger in an effectively final variable.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A colleague counts invalid orders with int[] invalid = {0}; orders.forEach(o -> { if (!valid(o)) invalid[0]++; }), because a plain int would not compile. Review the change, and say what happens when forEach becomes orders.parallelStream().forEach.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    You are converting a large codebase's anonymous Runnable, Comparator and listener classes to lambdas with an IDE quick-fix. Which conversions can change behaviour, and how would you find them before they reach production?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A lambda is an object of a functional interface. The interface is its target type, and it comes from the context: an assignment, a return, a method argument or a cast. The same text can have several types.
  • The target type also decides the parameter and result types: m is an int for IntPredicate and an Integer for Predicate<Integer>.
  • var f = x -> … and Object o = () -> … do not compile: neither gives the lambda a functional interface.
  • Local variables used in a lambda must be effectively final. Count with the stream, use a loop, or hold a mutable object such as an AtomicInteger.
  • this in a lambda is the object of the surrounding code, and a lambda parameter may not reuse a local’s name.
  • javac compiles a lambda body into a private method and an invokedynamic call site; the class is made in memory at run time. Never rely on the identity of a lambda object.
  • Keep lambdas short. Prefer a method reference when a method already does the job, and a named method when the body needs a name or a test.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 Both lines compile. Which statement about them is true?

    Read the code, then choose one answer.

    IntPredicate a = m -> m >= 40;
    Predicate<Integer> b = m -> m >= 40;
    Show the answer to question 1

    Answer: a and b are objects of different interfaces; in a the parameter m is an int, in b it is an Integer.

    A lambda takes its type from its context. The TargetTyping example printed the interface of each object (IntPredicate, Predicate, Function, MarksRule), and the parameter type comes from the interface's one abstract method: test(int) for IntPredicate, test(Integer) for Predicate<Integer>. Both test methods return a boolean.

  2. Question 2 of 5 Which declaration fails to compile?

    Choose one answer.

    Show the answer to question 2

    Answer: var rule = m -> m >= 40;

    var takes the variable's type from the right-hand side, and a lambda has no type until its context gives it one, so javac reports "lambda expression needs an explicit target-type". The cast in the third choice supplies that target type, so the lambda becomes an IntPredicate, which may then be stored in an Object variable.

  3. Question 3 of 5 marks.forEach(m -> { if (m < 40) failed++; }) does not compile because failed is a local int. Which change gives the count without breaking the rule for captured variables?

    Choose one answer.

    Show the answer to question 3

    Answer: long failed = marks.stream().filter(m -> m < 40).count();

    Letting the stream count leaves nothing to change, which is the simplest fix; a plain loop or an AtomicInteger also work. A final variable can never be incremented, a second lambda has the same problem, and local variables cannot be volatile at all.

  4. Question 4 of 5 What does show() print?

    Read the code, then choose one answer.

    class Report {
        void show() {
            Runnable r = () -> IO.println(this.getClass().getName());
            r.run();
        }
    }
    Show the answer to question 4

    Answer: Report

    Inside a lambda body, this means the same as in the code around it, here the Report object. An anonymous class would print Report$1, its own class, as the ThisDemo session showed.

  5. Question 5 of 5 A file declares one class whose methods contain one anonymous class and two lambda expressions, and nothing else. How many .class files does javac -d classes write for it?

    Type a number.

    Show the answer to question 5

    Answer: 2

    One for the class and one for the anonymous class (named like SortNames$1.class). The lambdas add none: their bodies become private methods of the class, and the JVM creates their classes in memory when the invokedynamic instructions first run, as the javap session showed.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.