How can a generic superclass cause a 'same erasure' clash in a subclass, and what role do bridge methods play?
answer
- Generic method erases to its bound (add(T) -> add(Object))
- Compiler emits synthetic ACC_BRIDGE method to forward erased call to the override
- Declaring the bridge's own signature causes a clash
- Two interfaces with same-erasure methods can't both be implemented
- Use javap -p -v to see bridges and real descriptors
basics
~20 sWhen you extend a generic class and the type argument fixes a method's parameter, your subclass method can erase to the same signature as another method, causing a clash. The compiler also adds hidden bridge methods so the erased and generic views stay consistent.
solid answer
~50 sGenerics are erased, so a generic supertype method like add(T) erases to add(Object). When a subclass binds T (e.g. class StringBox extends Box<String>) and overrides add(String), the erased override is add(String) but the inherited erased method is add(Object) — different descriptors. To keep raw callers and the generic override consistent, the compiler emits a synthetic bridge method add(Object) in the subclass that casts and forwards to add(String). The clash arises when a subclass independently declares a method whose erasure equals one of these generated or inherited erased signatures — for example declaring your own add(Object) alongside the override, or two interfaces whose methods erase identically. The compiler then reports a name clash because two override-equivalent methods (one possibly a bridge) cannot coexist. Diagnosing this requires reading the erased signatures, not the source-level generic ones, and remembering that bridges occupy real erased slots in the class.
code
java · 17 linesclass Box<T> {
void add(T t) {} // erased: void add(Object)
}
class StringBox extends Box<String> {
@Override void add(String s) {} // erased: void add(String)
// Compiler synthesizes a bridge:
// void add(Object o) { add((String) o); } // ACC_BRIDGE, ACC_SYNTHETIC
// The following WOULD clash with that generated bridge:
// void add(Object o) {} // error: same erasure as the bridge add(Object)
}
// Two interfaces whose methods erase identically cannot both be implemented:
interface A { void f(java.util.List<String> x); } // erased f(List)
interface B { void f(java.util.List<Integer> x); } // erased f(List)
// class C implements A, B {} // error: name clash, both f(List)go deeper
Aware that extending a generic class involves erasure and that some clashes come from the inheritance, without needing bridge-method detail.
Can describe that the compiler adds hidden methods to make overriding work and that two interfaces with type-argument-only differences cannot both be implemented.
Explains bridge-method generation, why the override's erased signature differs from the supertype's, and how declaring a colliding signature triggers the clash.
Reasons about hierarchy/interface design under erasure, predicts bridge generation, uses javap to verify descriptors, and explains the binary-compatibility rationale and ClassCastException-from-bridge behavior.
## Setup: erasure across inheritance A *generic* type's type variable is erased to its bound. So: ```java class Box<T> { void add(T t) {} } // erased: void add(Object) ``` When a subclass *binds* the type variable to a concrete type and overrides the method: ```java class StringBox extends Box<String> { @Override void add(String s) {} // erased: void add(String) } ``` Now there is a mismatch: the supertype's erased method is `add(Object)`, but the override's erased signature is `add(String)`. A caller holding a raw `Box` (or `Box<String>` compiled against the generic view) expects to call `add(Object)`. If only `add(String)` existed in `StringBox`, polymorphism would break for the erased call. ## Bridge methods (the compiler's fix) To reconcile this, the compiler generates a synthetic, bridge-flagged **bridge method** in `StringBox`: ```java // generated by the compiler, not in source: void add(Object o) { add((String) o); } ``` The bridge has the *supertype's erased signature* `add(Object)`, performs the cast, and forwards to the real override `add(String)`. This keeps both the raw/erased call path and the generic override working. Bridges are why a `ClassCastException` can surface from a line you 'never wrote' when raw types smuggle in a wrong-typed argument. ## How a clash appears Because bridges occupy real erased method slots, a subclass can collide with them or with inherited erased signatures: 1. **Declaring the bridge's signature yourself** ```java class StringBox extends Box<String> { void add(String s) {} void add(Object o) {} // clashes with the generated bridge add(Object) } ``` The compiler wants to generate `add(Object)` as a bridge but you already declared `add(Object)` — *name clash: ... have the same erasure*. 2. **Two supertypes whose methods erase identically** ```java interface A { void f(List<String> x); } interface B { void f(List<Integer> x); } class C implements A, B {} // f(List) vs f(List) — cannot implement both ``` No single method can satisfy both erased contracts → clash. 3. **Inherited generic + own raw method** Declaring a method whose erasure equals an inherited generic method's erasure produces the same override-equivalence error. ## The governing rule (recap, generalized) It is a compile-time error if a class ends up with two *override-equivalent* methods (same name, same erased parameter types), counting **generated bridge methods** as real members. That is the unifying principle behind every case above. ## Diagnosing in practice - Read the **erased** signatures, not the generic source. Mentally apply: parameterized type → raw type; type variable → leftmost bound. - Use `javap -p -v` on the `.class` to *see* the synthetic bridges (`ACC_BRIDGE`, `ACC_SYNTHETIC` flags) and confirm which descriptors actually exist. - For the two-interface case, you genuinely cannot implement both in one class — the design must change (separate adapters, or non-clashing method names). ## Why Java accepts this complexity Erasure was chosen for binary compatibility with pre-generics code and to avoid per-instantiation code bloat. Bridge methods are the price that preserves polymorphism under erasure. The clashes are emergent constraints of that design; senior/principal engineers design class hierarchies and interfaces so that erased signatures never collide, and reach for `javap` when a puzzling 'same erasure' or unexpected `ClassCastException` appears.
- What flags identify a bridge method in the class file, and how do you inspect them?Bridge methods carry the ACC_BRIDGE and ACC_SYNTHETIC access flags. You can see them with javap -p -v on the compiled class.
- Why can a ClassCastException originate from a bridge method?The bridge casts the Object argument to the bound type before forwarding. If raw-typed code passes a wrong-typed value, the cast inside the synthetic bridge throws, even though no such cast appears in your source.
saying these in an interview costs you the question
- Thinking bridge methods are something the developer writes
- Believing a subclass can implement two interfaces whose methods only differ by type argument
- Assuming the override's signature replaces rather than coexists with the erased supertype signature
- Saying bridges are a runtime JVM feature rather than compiler-generated synthetic methods