How does numeric promotion interact with method overload resolution and autoboxing when the compiler chooses which overloaded method to call?
answer
- Three phases: widen, then box, then varargs
- Widening beats boxing beats varargs
- Most-specific applicable method wins per phase
- No widening between wrapper types (Integer !-> Long)
- Phased design preserves pre-Java-5 behavior
basics
~20 sWhen picking an overloaded method, Java first tries widening primitives (like numeric promotion), then boxing, then varargs — in that order. So for a short argument it prefers a method taking int (widening) over one taking Integer (boxing).
solid answer
~50 sNumeric promotion and overload resolution share the same widening rules but operate in distinct phases. The compiler resolves overloads in three passes: (1) match using only subtyping and primitive *widening* (the numeric-promotion conversions) without boxing or varargs; (2) if none match, allow autoboxing/unboxing; (3) if still none, allow varargs. Each phase picks the *most specific* applicable method. Consequences: a `short` argument binds to `f(int)` over `f(Integer)` because widening (phase 1) beats boxing (phase 2). A `byte` binds to `f(int)` over `f(long)` because int is more specific (a narrower widening). Mixing matters: `f(int)` vs `f(long)` for an int argument picks `f(int)` (exact). Note widening between wrappers does NOT happen — an `Integer` will not widen to `Long`; it would have to unbox to int then widen, which the rules disallow. This phased model preserves backward compatibility from pre-generics Java while keeping the cheaper conversions preferred.
code
java · 9 linesstatic void g(long x) { System.out.println("long"); }
static void g(Integer x) { System.out.println("Integer"); }
int i = 5;
g(i); // "long": phase-1 primitive widening beats phase-2 boxing
static void h(Long x) {}
Integer boxed = 5;
// h(boxed); // COMPILE ERROR: Integer does not widen to Longgo deeper
Understands that overloaded methods exist and that an int argument can call a method taking a larger type like long.
Knows widening is preferred over boxing and can predict simple cases like f(int) vs f(Integer) for a short argument.
Articulates the three-phase JLS algorithm, most-specific selection, and the no-widening-between-wrappers rule with concrete examples.
Can reason about API design to avoid ambiguous overloads, the backward-compatibility rationale of the phased model, and pitfalls when adding boxed/varargs overloads to a published API.
## The problem overloading must solve **Overloading** means several methods share a name but differ in parameter types: `f(int)`, `f(long)`, `f(Integer)`, `f(Object)`, `f(int...)`. When you call `f(x)`, the compiler must pick exactly one at compile time. Numeric promotion's widening conversions are one of the tools it uses, but they're applied within a strict, staged algorithm so that adding autoboxing and varargs in Java 5 didn't change the meaning of existing code. ## The three phases (JLS 15.12.2) The compiler tries to find an applicable method in three successive phases; it only moves to the next phase if the current one finds nothing: 1. **Phase 1 — strict invocation**: applicability by *subtyping* and *primitive widening only*. No boxing, no unboxing, no varargs. The widening primitive conversions here are exactly the numeric-promotion ladder (`byte`->`short`->`int`->`long`->`float`->`double`, and `char`->`int`->...). 2. **Phase 2 — loose invocation**: like phase 1 but autoboxing and unboxing are now allowed. Still no varargs. 3. **Phase 3 — variable arity**: varargs (`int...`) are now considered. Within a phase, if multiple methods apply, the **most specific** one wins (roughly, the one whose parameter type can be passed to all the others). If none is strictly most specific, it's an ambiguity compile error. ## Worked consequences **Widening beats boxing.** Given `f(int)` and `f(Integer)` and a call `f(myShort)`: - Phase 1: `short` widens to `int`, so `f(int)` applies. `f(Integer)` needs boxing, not allowed in phase 1. -> `f(int)` is chosen. **More specific widening wins.** Given `f(int)` and `f(long)` and a call `f(myByte)`: - Phase 1: byte widens to both int and long, but `int` is more specific than `long`. -> `f(int)`. **Exact match wins.** `f(int)` and `f(long)` with an `int` argument -> `f(int)` (no conversion needed). **No widening between wrapper types.** Given `f(Long)` and a call `f(myInteger)`: - Phase 1 fails (`Integer` is not a subtype of `Long`). - Phase 2: unboxing `Integer`->`int` then widening `int`->`Long`? **Not allowed** — the rules permit unbox-then-widen-then-box? No. An `Integer` cannot become a `Long`; you'd get a compile error if `f(Long)` were the only option. Reference widening only follows the class hierarchy, and primitive widening only happens after unboxing to the *matching* primitive. This trips people up: `Integer` -> `long` (primitive) is allowed (unbox then widen), but `Integer` -> `Long` (wrapper) is not. **Boxing beats varargs.** `f(Integer)` and `f(int...)` with an `int` argument -> phase 2 finds `f(Integer)` before phase 3 even considers varargs. ## The famous Integer caching aside A related (but separate) gotcha: autoboxing uses `Integer.valueOf`, which caches -128..127, so `Integer a = 100, b = 100; a == b` is true but at 200 it's false. That's identity, not promotion, but it often appears in the same interview. ## A complete example ```java static void g(long x) { System.out.println("long"); } static void g(Integer x) { System.out.println("Integer"); } static void g(Object x) { System.out.println("Object"); } int i = 5; g(i); // prints "long": phase 1 widens int->long; Integer/Object need boxing (phase 2) ``` ## Deriving the answer To predict the chosen overload: (1) try strict (subtyping + primitive widening) and pick the most specific; (2) only if nothing applies, allow boxing/unboxing; (3) only then, varargs. Remember widening across *wrapper* types does not exist.
- For 'f(short)' and 'f(int)' with a byte argument, which is called?f(short). In phase 1 both apply via widening, but short is more specific than int (a byte-to-short widening is narrower/more specific), so f(short) wins.
- Given only 'f(Long)', does 'f(anInteger)' compile?No. Integer is not a subtype of Long (phase 1 fails) and there is no unbox-to-int-then-box-to-Long conversion (phase 2 fails). It is a compile error. f(long) would have compiled (unbox then widen).
saying these in an interview costs you the question
- Claiming boxing is tried before primitive widening
- Assuming Integer widens to Long like int widens to long
- Thinking varargs is considered in the same phase as fixed-arity matches
- Confusing overload resolution (compile time) with dynamic dispatch (runtime)