Skip to main content

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).

Quiz

Lambdas vs anonymous classes

1 / 2

List 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
KindExampleLambda equivalent
StaticInteger::parseIntstr -> Integer.parseInt(str)
Bound instanceInstant.now()::isAftert -> then.isAfter(t)
Unbound instanceString::toLowerCasestr -> str.toLowerCase()
ConstructorTreeMap::new() -> new TreeMap<>()
Array constructorint[]::newlen -> 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:

InterfaceSignatureExample
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​

ItemMove
42Lambdas for function objects; keep generic context for inference
43Method references when they shorten forwarding lambdas
44Standard java.util.function types; custom only when warranted

Next: Streams (Items 45–48).