Generics

15 questions found

How would you implement a generic builder pattern with a self-referential generic type parameter (the 'curiously recurring generic pattern', <T extends Builder<T>>) to support fluent method chaining that correctly returns the actual subclass type in an inheritance hierarchy?

Advanced
A naive Builder base class returning `this` typed as the base Builder type breaks fluent chaining across a subclass hierarchy (a subclass-specific method called after a base-class method would return the base type, losing access to the subclass's own additional methods) -- the self-referential generic bound <T extends Builder<T>> lets the base class's chaining methods return T (the actual concrete subclass type, established when a subclass declares `class ConcreteBuilder extends Builder<ConcreteBuilder>`) rather than the base type, preserving full access to subclass-specific methods throughout the entire fluent chain regardless of how many base-class methods are called along the way.
public abstract class Builder<T extends Builder<T>> {
    protected String name;
    @SuppressWarnings("unchecked")
    public T withName(String name) { this.name = name; return (T) this; }  // returns concrete subclass type T
}

public class CarBuilder extends Builder<CarBuilder> {
    private int wheels;
    public CarBuilder withWheels(int wheels) { this.wheels = wheels; return this; }
}

CarBuilder car = new CarBuilder().withName("Tesla").withWheels(4);  // chaining works across base AND subclass methods seamlessly
Real-world example A configuration builder hierarchy with a shared base Builder class and multiple specialized subclasses (DatabaseConfigBuilder, CacheConfigBuilder) uses the self-referential generic bound to ensure fluent chains freely mix base-class and subclass-specific configuration methods in any order without ever losing access to subclass methods partway through the chain.

Common follow-ups: What happens if a subclass forgets to correctly parameterize itself (e.g., extends Builder<SomeOtherBuilder> by mistake)?;Why is the unchecked cast to (T) inside the base class considered safe in this specific, well-established pattern?

Design Patterns in Java;OOP & Classes

How would you use a Class<T> token parameter to work around type erasure when a generic method genuinely needs runtime access to the actual type argument, such as for deserialization or reflection-based instantiation?

Intermediate
Since type erasure removes T's actual runtime identity from a generic method's own execution context, passing an explicit Class<T> object (a 'type token') as an additional method parameter provides the missing runtime type information the erased generic type parameter alone can't supply -- this well-established idiom is exactly how many reflection-based frameworks (JSON deserialization libraries, dependency injection containers) recover enough runtime type information to perform operations like instantiating a new instance of T or validating a cast against T's actual runtime class.
public static <T> T parseJson(String json, Class<T> type) {
    // 'type' provides the runtime type information T alone cannot supply due to erasure
    return someJsonLibrary.readValue(json, type);
}

User user = parseJson(jsonString, User.class);  // Class<User> token supplies what erasure removed

public static <T> T createInstance(Class<T> type) throws Exception {
    return type.getDeclaredConstructor().newInstance();  // reflection-based instantiation using the token
}
Real-world example A JSON deserialization library's readValue(String json, Class<T> type) method signature is the standard, widely-recognized solution to needing runtime type information for generic deserialization, letting calling code specify exactly what concrete type to deserialize into despite the method itself being generic and subject to erasure.

Common follow-ups: What's the more advanced 'super type token' technique (using an anonymous subclass) for recovering even more complex generic type information (like List<String> specifically, not just List)?;Why can't the method simply use T.class directly instead of requiring an explicit Class<T> parameter?

Reflection API;Serialization & Deserialization

How does variance (covariance, contravariance, invariance) apply differently to Java arrays versus Java generics, and what practical safety trade-off does this design difference represent?

Advanced
Java arrays are covariant (String[] is-a Object[]) yet enforce type safety only at RUNTIME (via ArrayStoreException on an invalid write); Java generics are, by default, invariant (List<String> is NOT a List<Object>, despite String being a subtype of Object) specifically BECAUSE this invariance lets the compiler catch type mismatches entirely at COMPILE time instead, avoiding array covariance's exact runtime-exception risk -- bounded wildcards (? extends T, ? super T) then let you deliberately opt INTO a form of controlled covariance/contravariance for generics on a case-by-case basis, giving you the flexibility of array-like variance when genuinely needed, but with the compiler still enforcing safety at compile time within that controlled scope (disallowing unsafe writes to a ? extends T reference, for instance) rather than deferring the check to a runtime exception the way arrays do.
// Arrays: covariant, but unsafe -- fails at RUNTIME
Object[] objArray = new String[3];
objArray[0] = 42;  // compiles, throws ArrayStoreException at runtime

// Generics: invariant by default, safe -- fails at COMPILE time instead
// List<Object> objList = new ArrayList<String>();  // COMPILE ERROR, caught immediately

// Generics with bounded wildcard: opt-in controlled covariance, STILL compile-time safe
List<? extends Object> objList2 = new ArrayList<String>();  // legal
// objList2.add("test");  // COMPILE ERROR -- can't write to a ? extends reference, still safe!
Real-world example A code review specifically flags a method signature using Object[] to accept different array element types (inheriting array covariance's runtime ArrayStoreException risk) and suggests refactoring to a generic method with a bounded wildcard instead, gaining equivalent flexibility while moving the safety check from a possible runtime exception back to a guaranteed compile-time error.

Common follow-ups: Why did the language designers choose different variance defaults for arrays versus generics, given both existed under the same type system?;What would Java generics look like if they had been designed to be covariant by default, similar to arrays?

Arrays & Multidimensional Arrays;Collections Framework

How would you write a generic interface (like a custom Repository<T, ID> pattern common in data-access layers) and implement it for a specific entity type?

Intermediate
A generic interface declares its type parameters at the interface level (interface Repository<T, ID>), with method signatures throughout the interface using those parameters, letting a concrete implementing class specify the actual types when it implements the interface (class UserRepository implements Repository<User, Long>), giving every method in that specific implementation properly typed parameters and return values without needing casts, while the interface itself remains fully reusable across entirely different entity types.
public interface Repository<T, ID> {
    T findById(ID id);
    List<T> findAll();
    T save(T entity);
    void deleteById(ID id);
}

public class UserRepository implements Repository<User, Long> {
    public User findById(Long id) { /* ... */ return null; }
    public List<User> findAll() { /* ... */ return List.of(); }
    public User save(User user) { /* ... */ return user; }
    public void deleteById(Long id) { /* ... */ }
}
Real-world example A data-access layer defines a single generic Repository<T, ID> interface, implemented separately for UserRepository (Repository<User, Long>), ProductRepository (Repository<Product, String>), and every other entity type, giving each implementation fully type-safe, properly-typed methods without any code duplication in the shared interface contract itself.

Common follow-ups: How does Spring Data JPA build heavily on exactly this generic repository interface pattern to auto-generate implementations?;What's the benefit of parameterizing the ID type separately from the entity type T, rather than assuming a fixed ID type?

Interfaces & Abstract Classes;Design Patterns in Java

How would you implement a generic pair/tuple utility method that returns different concrete types depending on the number of elements, and how does target-type inference help the compiler choose the correct generic instantiation from context alone?

Advanced
A well-designed generic factory method (like a static of() method) relies on the compiler's target-type inference to determine the appropriate type arguments purely from the context where the result is USED (the declared variable type, a method parameter type, or a return type), without requiring explicit type arguments at the call site -- this became notably more capable in later Java versions (target typing improvements), letting increasingly sophisticated inference scenarios (like inferring generic types through nested generic method calls, or from a lambda's inferred parameter types) resolve correctly without any explicit type witnesses, significantly reducing the verbosity that earlier, more limited inference would have required.
public class Pair<A, B> {
    private final A first;
    private final B second;
    private Pair(A first, B second) { this.first = first; this.second = second; }
    public static <A, B> Pair<A, B> of(A first, B second) { return new Pair<>(first, second); }
}

// Target-type inference determines A=String, B=Integer purely from the assignment context
Pair<String, Integer> pair = Pair.of("age", 30);
Real-world example A utility library's generic Pair.of(a, b) factory method relies entirely on target-type inference to determine its type arguments from how the result is used, letting calling code write concise, unadorned factory calls without ever needing to spell out explicit generic type arguments, even in fairly complex nested-generic scenarios.

Common follow-ups: What specific Java version introduced meaningfully improved target-type inference, and what scenarios did it newly support?;How does this inference interact with method overloading when multiple overloads could each plausibly match?

Functional Interfaces & Method References;Design Patterns in Java

Showing 11–15 of 15