Lambdas and Method References
Source: Effective Java, 3rd Edition — Chapter 7 by Joshua Bloch (Items 42–44)
Java 8 added functional interfaces, lambdas, and method references for concise function objects, alongside the streams API. This lesson: lambdas over anonymous classes, method references over verbose lambdas, and standard types from java.util.function.
Item 42: Prefer lambdas to anonymous classes
A functional interface has one abstract method. Lambdas implement it more succinctly than anonymous classes:
// Obsolete
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
});
// Prefer
words.sort((s1, s2) -> Integer.compare(s1.length(), s2.length()));
// or
words.sort(comparingInt(String::length));
The compiler infers lambda parameter and return types from context — keep generics on collections (List<String>, not raw List) so inference works (Items 26, 29, 30).
Enum constant behavior: pass a DoubleBinaryOperator lambda to each enum constructor instead of constant-specific class bodies when behavior is a short, self-explanatory line.
When not to use lambdas: abstract classes, interfaces with multiple abstract methods, need this to refer to the function object itself, or serialization (use private static nested class, Item 24). Keep lambdas to ~1–3 lines; enum constructor lambdas run in a static context (no instance field access).
Lambdas vs anonymous classes
1 / 2List words declared as raw List (not List<String>) with a sorting lambda
Pick an answer to see if you are right.
Item 43: Prefer method references to lambdas
When a lambda only forwards to an existing method, a method reference is often clearer:
map.merge(key, 1, (count, incr) -> count + incr); // verbose
map.merge(key, 1, Integer::sum); // better
| Kind | Example | Lambda equivalent |
|---|---|---|
| Static | Integer::parseInt | str -> Integer.parseInt(str) |
| Bound instance | Instant.now()::isAfter | t -> then.isAfter(t) |
| Unbound instance | String::toLowerCase | str -> str.toLowerCase() |
| Constructor | TreeMap::new | () -> new TreeMap<>() |
| Array constructor | int[]::new | len -> new int[len] |
Prefer method references when shorter and clearer; keep lambdas when parameter names document intent, or when the method is in the same class (() -> action() vs Humongous::action). x -> x is often cleaner than Function.identity().
Item 44: Favor the use of standard functional interfaces
java.util.function supplies 43 standard interfaces — prefer them over one-off functional interfaces when they fit. Six core object-reference types:
| Interface | Signature | Example |
|---|---|---|
UnaryOperator<T> | T apply(T) | String::toLowerCase |
BinaryOperator<T> | T apply(T,T) | BigInteger::add |
Predicate<T> | boolean test(T) | Collection::isEmpty |
Function<T,R> | R apply(T) | Arrays::asList |
Supplier<T> | T get() | Instant::now |
Consumer<T> | void accept(T) | System.out::println |
Plus primitive variants (IntPredicate, LongBinaryOperator, …). Use primitive functional interfaces for bulk numeric work — not boxed types (Item 61).
Write your own @FunctionalInterface when the type has a descriptive name, strong contract, or useful defaults — like Comparator, not ToIntBiFunction. Avoid overloadings that differ only by functional interface type in the same argument position (ExecutorService Callable vs Runnable ambiguity, Item 52).
Items 42–44 map
| Item | Move |
|---|---|
| 42 | Lambdas for function objects; keep generic context for inference |
| 43 | Method references when they shorten forwarding lambdas |
| 44 | Standard java.util.function types; custom only when warranted |
Next: Streams (Items 45–48).