How do you declare a generic class or method with multiple type parameters, and how are they bound to actual types?
answer
- Comma-separated placeholders: <K,V>, <T,U,R>
- Method type params go BEFORE the return type
- Binding is positional, like method arguments
- Params can reference each other / be bounded: <T,U extends T>
- Each param erased independently at runtime
basics
~10 sPut several comma-separated placeholders in the angle brackets, like class Pair<K,V> or <T,R> R map(T in). Each placeholder is filled independently when you use the class or call the method, e.g. Pair<String,Integer>.
solid answer
~40 sYou declare multiple type parameters as a comma-separated list inside the angle brackets: `class Pair<K, V> { K key; V value; }`. Each parameter is independent and can be bound to a different actual type, so `new Pair<String, Integer>(...)` makes K=String and V=Integer. For generic methods, the type parameter list goes before the return type: `static <T, R> R convert(T input, Function<T, R> f)`. There the parameters are usually inferred from the arguments, so you rarely write them explicitly. Type parameters can reference each other and have bounds, e.g. `<K extends Comparable<K>, V>` or even `<T, U extends T>`. Order matters only positionally — the first actual type argument binds to the first parameter. The JDK uses this everywhere: `Map<K,V>`, `BiFunction<T,U,R>`, `Map.Entry<K,V>`. Each parameter is erased independently at runtime.
go deeper
Can write a two-parameter generic like Pair<K,V> and instantiate it with two concrete types.
Can declare generic methods with multiple parameters, knows the list precedes the return type, and that binding is positional.
Comfortable with interdependent/bounded parameters (<T,U extends T>, recursive Comparable bounds) and explains independent erasure.
Designs multi-parameter generic APIs that read well and uses bounds to encode type relationships without over-constraining callers.
## Declaring multiple type parameters A generic type or method can take more than one placeholder. You list them comma-separated inside the angle brackets: ```java class Pair<K, V> { // two type parameters private final K key; private final V value; Pair(K key, V value) { this.key = key; this.value = value; } K getKey() { return key; } V getValue() { return value; } } ``` When you instantiate it, you supply one actual type per parameter, positionally: ```java Pair<String, Integer> p = new Pair<>("age", 42); // K is bound to String, V is bound to Integer ``` The parameters are **independent**: `Pair<String,String>`, `Pair<Long,User>` etc. are all valid. The first argument binds to the first parameter, the second to the second — order is positional, exactly like method arguments. ## Generic methods with multiple parameters For a method, the type-parameter list comes **before the return type**: ```java static <T, R> R convert(T input, Function<T, R> mapper) { return mapper.apply(input); } ``` Here `<T, R>` introduces two parameters used in the signature. Callers normally don't specify them — the compiler **infers** them from the arguments (`convert("5", Integer::parseInt)` infers T=String, R=Integer). You *can* supply them explicitly with the rarely-used 'type witness' syntax: `Util.<String,Integer>convert(...)`. ## Parameters can depend on each other and have bounds A later type parameter can refer to an earlier one, and any of them can be bounded with `extends`: ```java class SortedPair<K extends Comparable<K>, V> { ... } // K must be comparable to itself static <T, U extends T> void copy(List<T> dst, List<U> src) { ... } // U is a subtype of T ``` This is how you express relationships between the types. `K extends Comparable<K>` is the classic 'recursive' bound meaning 'K can be compared to other Ks'. ## Naming Use the conventional letters: `K,V` for key/value, `T,U,R` (or `T,S,U`) when the roles are generic, with `R` typically the return type. The standard library follows this: `Map<K,V>`, `Map.Entry<K,V>`, `BiFunction<T,U,R>`. ## Runtime: independent erasure Each type parameter is **erased** independently at compile time. At runtime `Pair<String,Integer>` and `Pair<Long,User>` are both just `Pair`; the type arguments are not available via `getClass()`. That's why you can't, for example, do `new K()` or `instanceof V` — the actual types are gone. The compile-time binding is what gives you safety: you can't put an Integer where a String key is expected. ## Common pitfalls - Mixing up positional order (`Pair<V,K>` vs `Pair<K,V>`) — the names are documentation; position is what binds. - Forgetting the method's `<T,R>` must come *before* the return type, not after the method name. - Expecting the actual types at runtime — they're erased.
- Where does the type-parameter list go in a generic method declaration?Immediately before the return type, e.g. `static <T, R> R convert(...)`. It must precede the return type, not follow the method name.
- Can a second type parameter be constrained by the first?Yes. You can write `<T, U extends T>`, meaning U must be a subtype of T. Later parameters may reference earlier ones in their bounds.
saying these in an interview costs you the question
- Putting the method's type-parameter list after the return type or method name
- Assuming type parameters must be unrelated (they can depend on each other)
- Thinking you must always specify method type arguments explicitly (usually inferred)
- Believing the actual type arguments are available at runtime