You have two pieces of logic that conceptually take a List<String> and a List<Integer>. How do you expose both given the erasure clash?
answer
- Rename = cleanest, intention-revealing
- Class<T> type token disambiguates AND recovers element type
- Different raw container types break the clash
- Collapse to one generic method if logic is type-independent
- Return-type-only change never works
basics
~10 sGive the methods different names, or make them differ in their raw parameter types, or add an extra parameter. You cannot keep the same name with only the type argument different.
solid answer
~50 sBecause both signatures erase to method(List), you must make the erased signatures differ or remove the duplication. Common options: (1) Rename the methods to be intention-revealing, e.g. addStringRows and addIntRows — usually the best choice. (2) Add a distinguishing parameter whose erasure differs, such as a Class<T> type token. (3) Change one parameter to a different collection or raw type so the raw types diverge. (4) If the two bodies are really the same logic, collapse them into one generic method <T> void method(List<T>) and branch on a type token if needed. Renaming is preferred because it is explicit and avoids surprising callers; the Class<T> token is useful when a generic API genuinely needs to dispatch on the runtime element type, but adds verbosity. Overloading on type arguments alone is simply not possible in Java, so the design must reflect that.
go deeper
Can suggest renaming the two methods and knows that you cannot keep one name with only the type argument differing.
Lists multiple workarounds (rename, type token, different raw type, single generic method) and picks an appropriate one for the situation.
Weighs trade-offs, explains why a Class<T> token both disambiguates and restores runtime type info, and avoids the return-type/instanceof anti-patterns.
Frames the choice as API-design: stable, readable surfaces over clever overloading; considers caller ergonomics, binary compatibility, and when a token-based dispatch belongs in a library API.
## The constraint you are working around In Java, `method(List<String>)` and `method(List<Integer>)` both erase to `method(List)` (the type arguments are removed at compile time), so the compiler rejects declaring both in one class — a *same-erasure name clash*. You therefore cannot overload purely on the type argument. Every workaround either (a) makes the *erased* signatures differ, or (b) eliminates one of the methods. ## Option 1 — Rename (recommended default) ```java void addStringRows(List<String> rows) { ... } void addIntRows(List<Integer> rows) { ... } ``` Why it is best: the names now communicate intent, call sites are unambiguous, and there is zero runtime cost. Overloading was only ever a cosmetic convenience here. ## Option 2 — Distinguishing parameter / type token Add a parameter whose erasure differs. A common idiom is a `Class<T>` *type token* so the method can also recover the element type at runtime: ```java <T> void method(List<T> list, Class<T> type) { if (type == String.class) { ... } else if (type == Integer.class) { ... } } ``` Now there is a *single* method; the token disambiguates behavior. Use this when a generic API must dispatch on the runtime element type (erasure otherwise hides it). Downsides: callers must pass `String.class`/`Integer.class`, and the type-check branching is less clean than separate methods. ## Option 3 — Different raw parameter types If the two inputs can legitimately be different container types, the raw types diverge and the clash disappears: ```java void method(List<String> a) { ... } void method(Collection<Integer> a) { ... } // erases to method(Collection) — distinct ``` This is fragile: it works only because `List` and `Collection` erase differently, and it can confuse readers. Prefer renaming. ## Option 4 — One generic method If the bodies are actually identical logic, you never needed two methods: ```java <T> void method(List<T> list) { ... } ``` This is the cleanest when the logic does not depend on the element type. ## Anti-patterns to avoid - **Changing only the return type** — return type is not part of the signature, so the clash remains; it does not compile. - **Casting to raw List at the call site to 'force' a choice** — there is only one erased method anyway; you cannot select a non-existent overload. - **Relying on `instanceof` of the list itself** — `list instanceof List<String>` is illegal (you can only test the raw `List`); element types are erased, so you cannot recover them from the list alone, which is exactly why a type token (Option 2) is needed. ## Choosing - Different intent → **rename** (Option 1). - Same logic, type-independent → **one generic method** (Option 4). - Generic API needing runtime element type → **type token** (Option 2). - Genuinely different containers → **different raw types** (Option 3), used sparingly.
- Why is a Class<T> token sometimes needed beyond just resolving the clash?Because erasure removes the element type at runtime, you cannot recover whether a List holds Strings or Integers from the list itself. A Class<T> token carries that type information so the method can dispatch on it.
- Is overloading on List<String> vs Set<String> allowed?Yes. They erase to List and Set, which are distinct raw types, so the signatures differ and both overloads compile.
saying these in an interview costs you the question
- Suggesting a different return type fixes the clash
- Trying list instanceof List<String> to branch — element type is erased
- Believing a raw-type cast at the call site can pick one overload
- Adding @SuppressWarnings and assuming it makes the overloads compile