Skip to content

Lab: turning a long lambda into a method reference

☕ Java 17 · How Lambdas See the Variables Around Them · 4/4

Goal: refactor a filter buried in a block lambda into a named method, then a method reference, and prove all versions behave the same.

Setup: create Movie.java and CatalogFilter.java in the same folder, with no package.

Step 1 — The data

public record Movie(String title, int minutes, double rating) { }

Step 2 — The "before" version

Start CatalogFilter.java:

import java.util.function.Predicate;

public class CatalogFilter {

    // Before: logic buried in a lambda
    Predicate<Movie> hit = m -> {
        int minMinutes = 90;
        double minRating = 4.0;
        return m.minutes() >= minMinutes
            && m.rating() > minRating;
    };

Braces, two locals and a return: a warning sign. The reader must work through every line to learn what's checked, and no name says "feature hit".

Try adding int m = 0; under minRating: the compiler answers variable m is already defined, because the body shares the scope of its parameter. Delete that line.

Step 3 — Name it, then reference it

    // Step 1: give the logic a name
    boolean isFeatureHit(Movie m) {
        return m.minutes() >= 90 && m.rating() > 4.0;
    }

    // Step 2: a one-line lambda...
    Predicate<Movie> p1 = m -> isFeatureHit(m);

    // Step 3: ...or a method reference
    Predicate<Movie> p2 = this::isFeatureHit;
  • isFeatureHit states the intent, is reusable and testable, and its m lives in its own method scope.
  • p1 only forwards its parameter, so it can become this::isFeatureHit: it fits Predicate<Movie> (takes a Movie, returns a boolean).

Step 4 — Prove they agree

    public static void main(String[] args) {
        CatalogFilter filter = new CatalogFilter();   // this::isFeatureHit needs a real object

        Movie[] catalog = {
            new Movie("Inception", 148, 4.8),
            new Movie("Night Drive", 95, 4.1),
            new Movie("Coffee Break Shorts", 22, 4.5),
            new Movie("The Endless Saga", 160, 3.2),
            new Movie("Ninety Minutes Flat", 90, 4.0)
        };

        for (Movie movie : catalog) {
            System.out.println(movie.title()
                + " -> before: " + filter.hit.test(movie)
                + " | lambda: " + filter.p1.test(movie)
                + " | method ref: " + filter.p2.test(movie));
        }
    }
}

Predict first: Ninety Minutes Flat passes on minutes (>=) but fails on rating (> 4.0, not >=).

$ javac Movie.java CatalogFilter.java && java CatalogFilter
Inception -> before: true | lambda: true | method ref: true
Night Drive -> before: true | lambda: true | method ref: true
Coffee Break Shorts -> before: false | lambda: false | method ref: false
The Endless Saga -> before: false | lambda: false | method ref: false
Ninety Minutes Flat -> before: false | lambda: false | method ref: false

Every row agrees. Same behavior, very different readability: this::isFeatureHit tells the whole story in one line. Refactoring changes how clearly code says something, never what it does.

Full code on GitHub

← The effectively final rule · Back to the series


🎓 Preparing the OCP Java 17 exam? Exam traps and practice questions for this section → (optional)