How does type erasure interact with multiple bounds, and how can reordering interface bounds affect the generated bytecode?
answer
- T erases to leftmost bound (Object if none)
- Non-leftmost bound methods → synthetic checkcast
- Reorder interfaces → casts move; erased signature changes
- Class bound is fixed leftmost — can't reorder
- Bound order in public API = binary compatibility concern
basics
~20 sWith generics, the compiler removes type info and replaces T with its leftmost bound's type. So in <T extends A & B>, T becomes A. Reordering the bounds can change which type is used as the erasure, which can change casts the compiler inserts.
solid answer
~50 sType erasure replaces a type parameter with the **erasure of its leftmost bound** (or `Object` if unbounded). For `<T extends A & B & C>`, the erasure is the erasure of `A`. This matters because the compiler uses the erasure for field/parameter types in bytecode and inserts **synthetic casts** when the leftmost bound lacks a method you call. If you call a method declared only on `B`, but `A` is the leftmost bound, the compiler emits a cast to `B` at the call site. So which bound is leftmost is not purely cosmetic: it changes the erased signature and the casts. With a class bound the class is forced first, but with interface-only bounds you choose the order, and putting the most-used interface first can reduce inserted casts. It also affects **bridge methods** when such a type parameter appears in overridable signatures. Functionally the code behaves the same; the difference is in generated bytecode and, marginally, in clarity.
go deeper
Knows generics are erased at compile time and that T roughly 'becomes' its bound; not expected to reason about cast placement.
Can state that T erases to the leftmost bound and that calling a method from a later bound needs a cast, with a simple example.
Explains how reordering interface bounds shifts inserted casts and changes erased signatures, and recognizes the binary-compatibility implication for public APIs.
Reasons about bridge-method generation, erased-signature stability across library versions, and how bound ordering choices interact with overriding and API evolution.
## Prerequisite concepts **Generics** are a compile-time feature; the JVM has limited generic awareness. **Type erasure** is the process by which the compiler removes generic type arguments and rewrites the code in terms of raw types so it runs on a JVM that doesn't track them. The rules: - An **unbounded** parameter `T` erases to `Object`. - A **bounded** parameter erases to the **erasure of its leftmost bound**. For `<T extends Number>`, T erases to `Number`; for `<T extends A & B & C>`, T erases to the erasure of `A`. ## Why the leftmost bound is special After erasure, every occurrence of `T` in fields, parameters, locals, and return types is rewritten to that leftmost-bound type. The compiler then **inserts casts** wherever the program relies on a capability that the erasure type doesn't statically have. Consider: ```java interface Walks { void walk(); } interface Swims { void swim(); } static <T extends Walks & Swims> void move(T t) { t.walk(); // walk() is on Walks (leftmost) — no cast needed t.swim(); // swim() is on Swims — compiler erases T to Walks, then casts to Swims } ``` Because T erases to `Walks` (the leftmost bound), the call `t.walk()` needs no cast, but `t.swim()` is compiled roughly as `((Swims) t).swim()`. The cast is a **synthetic checkcast** in the bytecode. ## Reordering interface bounds With **interface-only** bounds you may choose the order. Swap them: ```java static <T extends Swims & Walks> void move(T t) { t.swim(); // no cast now — Swims is leftmost t.walk(); // ((Walks) t).walk() — cast inserted here instead } ``` The set of casts shifts to whichever methods do **not** belong to the leftmost bound. Putting the most-frequently-called interface first minimizes inserted casts. (When a **class** bound exists it is forced leftmost, so you cannot move it; the choice exists only among interfaces.) ## Effect on erased signatures and binary compatibility The **erased signature** of a method that takes or returns `T` uses the leftmost bound. Two declarations like `<T extends A & B>` and `<T extends B & A>` can therefore produce **different erased signatures** (parameter/return types `A` vs `B`). For *public API*, changing bound order is a **binary-incompatible change** to the erased signature and can break already-compiled callers and override/bridge relationships — so don't reorder bounds in a published API casually. ## Bridge methods When a generic type with such a parameter is overridden, the compiler may synthesize **bridge methods** to preserve polymorphism after erasure. The erased (leftmost-bound) types determine the bridge signatures. Reordering bounds can alter which bridges are generated. ## Does behavior change? Functionally, no: the program produces the same results regardless of bound order, because the inserted casts always succeed (the value really does implement every bound). The differences are in **generated bytecode** (which casts/where), the **erased signatures**, and consequently **binary compatibility** and **bridge methods**. Performance impact is negligible in practice. ## First-principles summary Erasure ⇒ T becomes its leftmost bound. Methods from non-leftmost bounds get synthetic casts. With interface-only bounds you control which bound is leftmost, shifting where casts land and what the erased signatures look like — cosmetic for behavior, but meaningful for bytecode and public-API binary compatibility.
- In <T extends Walks & Swims>, calling t.swim() compiles to what at the bytecode level?Roughly ((Swims) t).swim() — a synthetic checkcast to Swims, because T erases to the leftmost bound Walks, which doesn't declare swim().
- Why is reordering bounds risky for a published library API?The erased signature uses the leftmost bound, so reordering can change parameter/return erasures and bridge methods, breaking binary compatibility for already-compiled callers and overrides.
saying these in an interview costs you the question
- Saying reordering bounds changes program behavior — it changes bytecode/casts, not results.
- Assuming erasure picks the class bound regardless of position — it picks the LEFTMOST bound; that's WHY the class must be first.
- Thinking bound order never matters — it affects erased signatures and binary compatibility of public APIs.