Explain the JLS rule that governs the 'same erasure' overload clash and what 'override-equivalent' means.
answer
- Override-equivalent = one signature is a subsignature of the other
- Subsignature bridges generic and raw via erasure
- Type variable erases to leftmost bound (unbounded -> Object)
- Bridge methods exist because of this same-erasure machinery
- Return type and type-parameter names are ignored
basics
~10 sJava forbids two methods in a class whose names and erased parameter types match. After erasure the generic parts are gone, so such methods would be indistinguishable, which is the rule the compiler enforces.
solid answer
~50 sThe JLS says it is a compile-time error for a class to declare two methods with override-equivalent signatures. Two method signatures are override-equivalent if either is a subsignature of the other, and a key part of that test is having the same erasure of the parameter types. Erasure maps every parameterized type to its raw type (List<String> and List<Integer> both to List) and type variables to their leftmost bound. So even though method(List<String>) and method(List<Integer>) are distinct at source level, they are override-equivalent after erasure, and the class cannot declare both. This same rule explains why a subclass method can clash with a superclass generic method, and why bridge methods are sometimes generated: the compiler must keep the erased view consistent with both the generic and raw worlds. The rule is purely about parameter-type erasure; return types and type-parameter names are not considered.
go deeper
Knows generics are erased and that identical erased methods are illegal, without needing the formal JLS vocabulary.
Can apply the erasure rules (parameterized->raw, type var->bound) to predict whether two methods clash.
States the override-equivalent / subsignature rule, explains erasure of type variables to bounds, and connects it to bridge methods.
Reasons about API and library evolution under erasure, when bridges are generated, binary-compatibility consequences, and how to design surfaces that never depend on type-argument-only overloads.
## What the rule actually says From the Java Language Specification (JLS, the official Java rules): *'It is a compile-time error for a class to declare two methods with override-equivalent signatures.'* And separately: two methods have the *same signature* if they have the same name and *argument types*, where argument-type comparison is done after considering type-variable substitution; but for the *clash* check the decisive notion is the **erasure** of the parameter types. ## Defining each term - **Signature**: method name + ordered list of formal parameter types. (Return type and `throws` are excluded.) - **Erasure**: the transformation that removes generics from a type. Rules: a parameterized type `C<T1..Tn>` erases to the raw type `C`; a type variable erases to the erasure of its *leftmost bound* (`<T>` with no bound → `Object`, `<T extends Number>` → `Number`); arrays erase component-wise; everything else stays. So `List<String>` → `List`, `Map<K,V>` → `Map`, `T` (unbounded) → `Object`. - **Subsignature**: signature A is a subsignature of B if A equals B, or A equals the *erasure* of B. This deliberately bridges the generic and raw worlds (so a generic method can override/match a raw one). - **Override-equivalent**: two signatures where one is a subsignature of the other. If two *declared* methods in one class are override-equivalent, that is the illegal clash. ## Worked example ```java class C { void m(List<String> a) {} void m(List<Integer> a) {} // ERROR } ``` Erase parameters: both → `m(List)`. Each signature is a subsignature of the other (they share the same erasure), so they are override-equivalent → compile error: *'name clash: ... have the same erasure'*. ## Why type variables follow the same logic ```java <T> void m(T t) {} void m(Object o) {} // ERROR — T erases to Object ``` `<T>` (unbounded) erases to `Object`, so `m(T)` and `m(Object)` collide. ## Connection to bridge methods When a generic class is subclassed/implemented, the compiler may emit a synthetic **bridge method** so that the erased (raw) caller and the generic override line up. The same-erasure machinery is what makes bridge methods necessary and also what can cause cross-class clashes (e.g. accidentally overriding the bridge). Understanding the rule is understanding why bridges exist. ## What is NOT considered - **Return type** — irrelevant to the signature, so it can never resolve a clash. (One narrow exception: covariant return types are allowed *only when one method legitimately overrides another*, never to disambiguate two same-class declarations.) - **Type-parameter names** — `<T> void m(T)` and `<U> void m(U)` are the same after renaming. - **Bounds in the source** — `List<? extends Number>` and `List<Integer>` both erase to `List`. ## Practical implication for API design Because the rule is erasure-based, you cannot design an API that overloads purely on type arguments. If callers should pick behavior by element type, use distinct method names, a marker parameter (`Class<T> type`), or a single generic method. This is a deliberate, accepted limitation of Java's erasure-based generics.
- To what does an unbounded type variable <T> erase, and what about <T extends Number & Comparable<T>>?Unbounded <T> erases to Object. With multiple bounds it erases to the leftmost bound, so <T extends Number & Comparable<T>> erases to Number.
- Why does this rule make bridge methods necessary?A generic override has a different erased signature from the raw supertype method, so the compiler emits a synthetic bridge with the supertype's erased signature that forwards to the real override, keeping raw callers working.
saying these in an interview costs you the question
- Claiming override-equivalence considers the return type
- Saying type variables stay as-is rather than erasing to their bound
- Confusing override-equivalence (the clash rule) with overload resolution (call-site choice)
- Asserting <? extends Number> erases to Number rather than the raw List