Skip to content

Part 8 recap: functional programming

☕ Java 17

Everything from Part 8 on one page. Each block links back to its section if you want the details.

Lambda syntax

A lambda is a method with no name, passed around like a value.

Predicate<Movie> a = m -> m.hd();                     // shortest form
Predicate<Movie> b = (Movie m) -> m.hd();             // explicit type
Predicate<Movie> c = (var m) -> m.hd();               // var
Predicate<Movie> d = m -> { return m.hd(); };         // block body
BiPredicate<Movie, User> e = (m, u) -> u.canWatch(m); // two parameters
Supplier<Movie> f = () -> new Movie("Pilot", 2024);   // no parameter
  • Parentheses can be dropped only for exactly one parameter with no type.
  • No braces: one expression, no return, no ;. Braces: a real block, with return when a value is expected and ; after every statement.
  • One style for all parameters: none, all typed, or all var.

→ Passing Behavior Around with Lambdas

Functional interfaces

A functional interface has exactly one abstract method, the one the lambda implements.

@FunctionalInterface
interface ContentFilter {
    boolean keep(Movie m);                                  // the one that counts
    default ContentFilter not() { return m -> !keep(m); }  // has a body: free
    static ContentFilter all() { return m -> true; }       // has a body: free
    String toString();                                      // from Object: free
}

default, static and private methods have a body, so they don't count. Abstract methods with the exact signature of an Object method (toString(), equals(Object), hashCode()) don't count either. @FunctionalInterface is optional: it only asks the compiler to check the rule. → The One-Method Contract Behind Every Lambda

Method references

Kind Example Same as
Static method Math::round x -> Math.round(x)
Method on a known object history::contains t -> history.contains(t)
Method on the parameter Movie::title m -> m.title()
Constructor Watchlist::new () -> new Watchlist()

Use :: only when the lambda just forwards its parameters to one method: m -> m.rating() > 4 stays a lambda. → Passing Methods Around by Name

Built-in interfaces

Interface Method In → out
Supplier<T> get() nothing → T
Consumer<T> / BiConsumer<T,U> accept 1 or 2 → nothing
Predicate<T> / BiPredicate<T,U> test 1 or 2 → boolean
Function<T,R> / BiFunction<T,U,R> apply 1 or 2 → R
UnaryOperator<T> / BinaryOperator<T> apply T (or T, T) → T

Count what goes in, check what comes out. Primitive versions (IntSupplier, DoublePredicate, ToIntFunction<T>…) avoid boxing and rename their methods: getAsBoolean(), applyAsInt()…

They combine without rewriting logic: p.and(q), p.or(q), p.negate(); f.andThen(g) runs f first, f.compose(g) runs g first:

Function<Integer, Integer> addAd = s -> s + 30;
Function<Integer, Integer> twice = s -> s * 2;
twice.compose(addAd).apply(60);   // (60 + 30) * 2 = 180
twice.andThen(addAd).apply(60);   // 60 * 2 + 30 = 150

→ The Ready-Made Lambda Toolkit

Variables and lambdas

A lambda can use a local variable or parameter only if it's final or effectively final: never reassigned, anywhere in the method. The error shows on the lambda line. Fields are always fine, and can even be changed. Lambda parameters and block variables can't reuse a name already in scope. → How Lambdas See the Variables Around Them

Everything together

import java.util.List;
import java.util.function.*;

public class RecentDramas {
    enum Genre { DRAMA, COMEDY }
    record Movie(String title, int year, Genre genre) { }

    public static void main(String[] args) {
        List<Movie> catalog = List.of(
            new Movie("Midnight Harbor", 2022, Genre.DRAMA),
            new Movie("Laugh Track", 2021, Genre.COMEDY),
            new Movie("Old Wounds", 2018, Genre.DRAMA),
            new Movie("Silent Shore", 2023, Genre.DRAMA));

        Predicate<Movie> recent = m -> m.year() >= 2020;
        Predicate<Movie> drama = m -> m.genre() == Genre.DRAMA;
        Function<Movie, String> label = Movie::title;
        Consumer<String> show = System.out::println;

        for (Movie m : catalog)
            if (recent.and(drama).test(m))
                show.accept(label.apply(m));
    }
}
Midnight Harbor
Silent Shore

Each business rule is its own Predicate, method references replace forwarding lambdas, and nothing runs until test(), apply() or accept() is called: deferred execution.

Code: Java17_Part_8_Functional_Programming on GitHub

🎓 Preparing the OCP exam? Test yourself with the Part 8 practice questions (optional).