A lambda is a poly expression. What concrete problems does that cause with method overloading, and how do you resolve a resulting ambiguity?
answer
- poly expression ⇒ no fixed type ⇒ overload resolution can't choose
- compiler prunes by arity + void/value compatibility
- two applicable functional-interface overloads ⇒ ambiguous
- fix: cast `(Runnable) () -> ...` or typed variable
- API lesson: don't overload on different functional interfaces
basics
~20 sBecause a lambda has no fixed type, when a method is overloaded with two functional-interface parameters that both fit the lambda, the compiler can't choose and reports an ambiguity. You fix it by giving the lambda a concrete type — usually with an explicit cast like (Runnable) () -> ....
solid answer
~50 sA lambda is a **poly expression** — it has no type until a target type is supplied. With overloaded methods this creates trouble: if `m(Runnable)` and `m(Callable<String>)` both exist, the lambda `() -> "x"` is compatible with both (a `Runnable` ignoring the return value, or a `Callable` returning the string), so the call is **ambiguous** and won't compile. The compiler uses the lambda's *shape* — arity and whether the body produces a value — to narrow candidates, but when two functional-interface overloads remain applicable it cannot pick a most-specific one. **Resolutions:** (1) **cast** the lambda to the intended interface — `m((Callable<String>) () -> "x")`; (2) assign it to a typed variable first and pass that; (3) use a **method reference** if it resolves unambiguously; or (4) avoid overloading methods on different functional interfaces in your own API design. The cast is the standard, localized fix.
code
java · 8 linesimport java.util.concurrent.Callable;
static void run(Runnable r) { /* ... */ }
static <T> void run(Callable<T> c) { /* ... */ }
// run(() -> "hello"); // ERROR: ambiguous (value-compatible fits both)
run((Callable<String>) () -> "hello"); // OK: cast pins the target type
run(() -> System.out.println("hi")); // OK: void-compatible -> only Runnable appliesgo deeper
Awareness that overloaded methods plus lambdas can produce a compile error is enough.
Can recognize the ambiguity and fix it with an explicit cast or a typed variable.
Explains void- vs value-compatibility, how shape prunes candidates, and the API-design rule against overloading on functional interfaces.
Designs APIs to avoid these traps (distinct method names, not overloads), and can reason about the interaction with generic method type inference and target typing across the call site.
## Recap: poly expression A **poly expression** is one whose type depends on context. A lambda is the canonical example — `() -> "x"` has no inherent type; the compiler assigns it one from the **target type**. Overload resolution is where this contextual nature bites, because overload resolution is itself about *choosing* the target. ## Why overloading clashes with lambdas Normally the compiler picks an overload by finding the **most specific** applicable parameter type for the argument. With a lambda argument there is no argument type to compare — only a lambda *shape*. The compiler does use the shape to prune: - **Arity** — the lambda's parameter count must match the interface's abstract method. - **Void-compatible vs value-compatible body** — an expression-statement body (e.g. `() -> doSomething()`) is *void-compatible*; an expression that yields a value (e.g. `() -> "x"`) is *value-compatible*. A block body is judged by whether all its `return`s carry a value. If, after pruning, **two or more functional-interface overloads are still applicable**, and neither's interface is more specific than the other, the call is **ambiguous** and fails to compile. ### Concrete example ```java void run(Runnable r) { /* ... */ } <T> void run(Callable<T> c) { /* ... */ } run(() -> "hello"); // AMBIGUOUS ``` `() -> "hello"` is value-compatible. It fits `Callable<String>` (returns the string) and *also* fits `Runnable` — a `Runnable` body may be an expression whose value is simply discarded. Both overloads apply; neither `Runnable` nor `Callable` is a subtype of the other, so there is no most-specific choice. ## How to resolve it 1. **Explicit cast (standard fix):** state the target type inline. ```java run((Callable<String>) () -> "hello"); ``` The cast *is* a target-typing context, so the lambda's type is pinned and only the matching overload applies. 2. **Typed intermediate variable:** ```java Callable<String> c = () -> "hello"; run(c); ``` Now the argument has a concrete type; ordinary overload resolution works. 3. **Method reference**, when it resolves unambiguously to one interface. 4. **API design:** avoid declaring overloads that differ only in functional-interface parameter type. This is a known JDK lesson — for example, designers often give the methods *different names* (`submit` vs `execute`) instead of overloading. ## Void- vs value-compatibility nuance A subtle case: `() -> System.out.println("x")` is an expression statement (the call returns `void`), so it is **void-compatible only**, and it would match `Runnable` but **not** `Callable<T>`. Changing the body from a value expression to a void expression can therefore *change* which overloads apply — a frequent source of confusion when refactoring. ## Why the language is built this way Lambdas were retrofitted onto a nominal type system that already had overloading. Rather than inventing structural function types, Java made lambdas poly expressions resolved by target typing. The cost is exactly these overload ambiguities; the benefit is that lambdas integrate seamlessly with the vast existing library of single-method interfaces.
- Why is `run(() -> "x")` ambiguous when overloads `run(Runnable)` and `run(Callable<String>)` exist?`() -> "x"` is value-compatible: it fits `Callable` (returns the string) and also `Runnable` (a value expression whose result is discarded). Both overloads apply and neither interface is more specific, so the compiler cannot choose — a compile error. Casting the lambda to the intended interface fixes it.
- Does `() -> System.out.println("x")` have the same ambiguity?No. That body is an expression *statement* returning void, so it is only void-compatible. It matches `Runnable` but not `Callable<T>`, so the overload resolves unambiguously to `run(Runnable)`.
saying these in an interview costs you the question
- Claiming overload resolution sees a lambda's 'class' to choose the overload
- Thinking the ambiguity is a runtime error rather than a compile error
- Believing a return-value lambda can never match a `Runnable` (it can — the value is discarded)
- Resolving ambiguity by changing the lambda body's logic instead of casting