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, withreturnwhen 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));
}
}
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).