skip to content

When should you use a generic method with a type parameter instead of a method that takes a wildcard argument?

level: seniorimportance: should knowfreq 40%

answer

  1. Name `<T>` to relate two places (e.g. return tied to arg)
  2. Wildcard `?` when the type appears once / only consumed
  3. Appears-exactly-once → prefer wildcard (Effective Java)
  4. PECS: Producer Extends, Consumer Super
  5. Capture helper: private `<U>` method captures a wildcard

basics

~20 s

Use a named type parameter (<T>) when you need to relate two things — like returning the same type you receive, or linking two arguments. Use a wildcard (?) when you only consume the argument and don't need to name its type anywhere else.

solid answer

~50 s

A generic method names a type parameter so it can be *referred to in more than one place* — two parameters that must match, or a return type tied to an argument type. If a type appears only once and you never need to name it, a wildcard (`<? extends X>` / `<? super X>`) is simpler and exposes less in the signature. The classic rule: if a type parameter appears exactly once in the method signature and the body doesn't depend on relating it elsewhere, replace it with a wildcard. Conversely, you *need* the named parameter to express a relationship — `<T> T max(List<T> list)`, `<T> void copy(List<? super T> dst, List<? extends T> src)` — or when the body must create or return values of that exact type. There's also a known trick: a private generic 'capture helper' method can capture a wildcard so the body can name it. Wildcards keep call sites clean; named parameters keep relationships expressible.

code

java · 10 lines
java
// Named T needed: return tied to argument
static <T> T first(List<T> list) { return list.get(0); }

// Wildcard enough: consume only, type used once
static void printAll(List<?> list) { list.forEach(System.out::println); }

// Both: relationship via T, direction via wildcards (PECS)
static <T> void copy(List<? super T> dst, List<? extends T> src) {
    for (T x : src) dst.add(x);
}

go deeper

for a junior

Knows both <T> and ? exist; can read a wildcard signature.

for a middle

Applies PECS and picks a wildcard for consume-only arguments.

for a senior

Decides between named parameter and wildcard via the appears-once rule and relationship needs; combines both in a copy-style signature.

for a principal

Sets API conventions to minimize signature complexity for callers; knows the capture-helper idiom and when wildcard-clean public APIs are worth a private generic helper.

## Two ways to make a method flexible about types When a method should work with many types, Java gives you two tools: 1. **A generic method with a named type parameter:** `<T> void m(List<T> list)`. 2. **A method with a wildcard argument:** `void m(List<?> list)` (unbounded), or bounded `List<? extends Number>` / `List<? super Integer>`. A **wildcard** `?` means 'some specific but unknown type.' `? extends X` means 'X or some subtype' (you can *read* X out of it — a *producer*). `? super X` means 'X or some supertype' (you can *write* X into it — a *consumer*). This is the **PECS** mnemonic: *Producer Extends, Consumer Super.* ## The core decision rule **Use a named type parameter when you need to *name the type more than once* — to express a relationship.** A wildcard cannot be referred to elsewhere, so it can't tie two things together. Relationships that force a named parameter: - **Return type tied to an argument:** `<T> T first(List<T> list)` — the return must be exactly the element type. With `List<?>` you could only return `Object`. - **Two arguments that must agree:** `<T> void swap(List<T> a, List<T> b)` or merging two lists of the same `T`. - **The body must produce/relate values of the exact type.** ## The 'appears once' guideline (Effective Java) If a type parameter appears **exactly once** in the method signature and isn't needed to relate anything, prefer a wildcard — it's simpler for callers and leaks less. For example: ```java // Over-engineered — T used once: static <T> void printAll(List<T> list) { ... } // Simpler — same capability: static void printAll(List<?> list) { ... } ``` Both accept a list of anything; the wildcard version is cleaner because the body only *consumes* the list. ## Combining both for max flexibility The most flexible signatures use a named parameter for the *relationship* and wildcards for the *direction of data flow*: ```java static <T> void copy(List<? super T> dest, List<? extends T> src) { for (T item : src) dest.add(item); } ``` Here `T` names the element relationship, `? extends T` says `src` is a producer (read from), and `? super T` says `dest` is a consumer (written to). ## The capture-helper trick Sometimes a public method takes a wildcard for a clean API, but the *body* needs to name that captured type (e.g. to swap two elements of the same unknown type). You add a **private generic helper** that captures the wildcard: ```java public static void swap(List<?> list, int i, int j) { swapHelper(list, i, j); // wildcard captured into U } private static <U> void swapHelper(List<U> list, int i, int j) { U tmp = list.get(i); list.set(i, list.get(j)); list.set(j, tmp); } ``` The public signature stays wildcard-clean; the helper names `U` so the body type-checks. ## Mental model Name a type (`<T>`) when you must *say it again somewhere*; use a wildcard (`?`) when you only ever *mention it once and just consume or produce*. Wildcards favor the caller; named parameters favor expressiveness of relationships.

  • Why can't `List<?> first(...)` return the exact element type?
    `?` is an unknown type the method can't name, so the most it can promise as a return type is `Object`. To return the exact element type you must name it: `<T> T first(List<T> list)`.
  • What is the capture-helper pattern for?
    It keeps a public method's signature wildcard-clean while letting the body name the captured type. The public method delegates to a private generic method (`<U>`), which captures the wildcard so operations like swapping two elements type-check.

saying these in an interview costs you the question

  • Using a named type parameter when it appears only once and adds no relationship
  • Thinking a wildcard can be returned as its exact type (it can only be Object)
  • Believing wildcards and named parameters are interchangeable in all cases
  • Forgetting that a wildcard can't be written to unless it's `? super X`

context