Why do lambdas and functional interfaces make passing a strategy lightweight in Java, compared with the classic Strategy implementation?
answer
- Functional interface = exactly one abstract method (SAM)
- Lambda/method ref synthesizes the strategy instance
- Classic: class-per-strategy or anonymous class boilerplate
- Comparator combinators: comparing / thenComparing / reversed
- Strategy becomes 'pass a function', not 'scaffold a pattern'
basics
~20 sA functional interface has just one method, so instead of writing a whole class to hold a strategy, you can write it inline as a short lambda. Comparator is functional, so 'sort by age' becomes one line instead of a new class.
solid answer
~50 sClassic Strategy requires a strategy interface plus a separate concrete class for each algorithm, often instantiated and wired up by hand — a lot of boilerplate for tiny behaviors. Java's lambdas remove that. A functional interface is any interface with exactly one abstract method (SAM); Comparator, Runnable, Function, and Predicate all qualify. Because the compiler knows there's only one method to implement, you can supply the strategy as a lambda or method reference — `(a, b) -> a.age() - b.age()` or `Comparator.comparingInt(Person::age)` — and the compiler synthesizes the implementation. No named class, no fields, no ceremony. This makes a strategy as cheap to pass as an argument, so Strategy stops being a 'pattern you set up' and becomes everyday code. Comparator's static/default combinators (comparing, thenComparing, reversed) further let you build composite strategies fluently, which the classic class-per-strategy form made awkward.
code
java · 14 lines// Classic Strategy: a class per ordering
class ByAge implements Comparator<Person> {
public int compare(Person a, Person b) { return Integer.compare(a.age(), b.age()); }
}
list.sort(new ByAge());
// Lambda / method-reference Strategy: one line, no class
list.sort((a, b) -> Integer.compare(a.age(), b.age()));
list.sort(Comparator.comparingInt(Person::age));
// Composed strategy via combinators
list.sort(Comparator.comparing(Person::name)
.thenComparing(Person::age)
.reversed());go deeper
Knows a lambda lets you write a Comparator inline in one line instead of a separate class.
Defines a functional interface (one abstract method), shows lambda and method-reference forms, and contrasts them with anonymous/named classes for Strategy.
Explains why this collapses Strategy's cost, uses Comparator combinators to compose strategies, and notes non-capturing lambdas are typically reused (no per-call allocation).
Discusses invokedynamic/lambda metafactory mechanics, capturing vs non-capturing allocation trade-offs, and how SAM conversion shifts API design toward passing behavior as values.
## Recap: the Strategy pattern Strategy = define a family of interchangeable algorithms behind a common interface and choose one at runtime. The cost of the *classic* form (pre-Java-8) was boilerplate. ## The classic, heavyweight form To vary sort order the old way you wrote a named class implementing the strategy interface: ```java class ByAge implements Comparator<Person> { public int compare(Person a, Person b) { return Integer.compare(a.age(), b.age()); } } // ... list.sort(new ByAge()); ``` Each new ordering = a new top-level or inner class, or at best a verbose **anonymous class**: ```java list.sort(new Comparator<Person>() { public int compare(Person a, Person b) { return Integer.compare(a.age(), b.age()); } }); ``` That's ~5 lines of ceremony to express one line of logic. Strategy worked, but felt expensive, so people under-used it. ## What a functional interface is A **functional interface** is an interface with exactly **one abstract method** (sometimes called a SAM type — Single Abstract Method). It may carry `default`/`static` methods too; only the count of *abstract* methods must be one. The optional `@FunctionalInterface` annotation makes the compiler enforce this. Examples in the JDK: `Comparator<T>` (compare), `Runnable` (run), `Function<T,R>` (apply), `Predicate<T>` (test), `Supplier<T>` (get). Because there is only one method to fill in, the compiler can accept a **lambda expression** or **method reference** wherever a functional interface is expected, and generate the implementing instance for you. ## Lambdas: the lightweight form A **lambda** is an anonymous function literal: `(parameters) -> body`. When the target type is a functional interface, the lambda *becomes* an instance of it: ```java Comparator<Person> byAge = (a, b) -> Integer.compare(a.age(), b.age()); list.sort(byAge); list.sort((a, b) -> Integer.compare(a.age(), b.age())); // inline, no variable ``` A **method reference** (`ClassName::method`) is shorthand for a lambda that just calls one method: ```java list.sort(Comparator.comparingInt(Person::age)); ``` The whole strategy is now a single expression — no class, no fields, no constructor. ## Why this matters for Strategy specifically 1. **Cost collapses.** Passing a strategy is now as cheap as passing any other argument, so you reach for it freely instead of avoiding the boilerplate. 2. **Strategies become anonymous and local.** Many orderings are one-off; you no longer pollute the codebase with a `ByAge`, `ByNameDesc`, etc., class for each. 3. **Composition via combinators.** `Comparator` ships static/default methods that build *new* strategies from existing ones — the fluent way to express composite ordering: ```java Comparator<Person> byNameThenAge = Comparator.comparing(Person::name) .thenComparing(Person::age) .reversed(); ``` `comparing`, `comparingInt`, `thenComparing`, and `reversed` each return a Comparator, so you assemble complex strategies declaratively — far cleaner than hand-writing a multi-key compare. ## Performance/semantics note Lambdas compile to `invokedynamic` and are typically realized without creating a new class file per lambda; a non-capturing lambda (like a stateless comparator) is generally instantiated once and reused. So 'lightweight' is true at the source level *and* avoids the per-strategy class-file proliferation of the anonymous-class era. Capturing lambdas (closing over local variables) may allocate per use, but for typical comparators this is negligible. ## Bottom line Functional interface + lambda turns Strategy from 'a pattern you scaffold' into 'pass a function'. Comparator is the showcase: one abstract method, rich combinators, and direct lambda support make swapping ordering strategies a one-liner.
- Can a functional interface have more than one method?Yes — it can have any number of default and static methods, but exactly one abstract method. Comparator, for instance, has many default/static helpers but only compare is abstract.
- How do you build an ordering 'by name, then by age descending' without writing a class?Compose combinators: Comparator.comparing(Person::name).thenComparing(Comparator.comparing(Person::age).reversed()).
saying these in an interview costs you the question
- Saying a functional interface can have only one method total (it can have default/static methods; only one *abstract*)
- Believing lambdas remove the interface — they still implement Comparator
- Claiming every lambda allocates a new object every call (non-capturing ones are typically reused)
- Thinking @FunctionalInterface is required to use a lambda (it only enforces the SAM rule)