Detailed explanation
Java generics let classes, interfaces, and methods declare type parameters, allowing reusable code to work with different reference types while preserving compile-time type safety. For example, List<String> describes a list whose elements are strings, so adding an Integer is rejected by the compiler. Without generics, using a raw List allows mixed values and requires explicit casts when reading elements. Generics move many type errors from runtime to compilation and make APIs communicate their intended types more clearly.
In the example class Box<T>, T is a placeholder used consistently by the value field, set(T) method, and get() method. When you write Box<String> b = new Box<>(), the compiler checks b.set("hello") as valid and treats b.get() as returning String. Calling b.set(10) fails compilation. The diamond operator, <>, lets the compiler infer the constructor's type arguments from context. Methods can also introduce their own type parameters, such as static <T> T identity(T value), independently of whether their enclosing class is generic.
Java implements generics mainly through type erasure. During compilation, a type parameter is replaced with its upper bound, or Object when no bound is declared. The compiler inserts casts where needed and may generate bridge methods to preserve overriding behaviour. Consequently, Box<String> and Box<Integer> share the same runtime class; an ordinary instance does not retain its supplied type argument as a directly inspectable runtime type. However, it is inaccurate to say reflection reveals no generic information: declarations such as a field of type List<String> can retain generic signature metadata that reflection can inspect.
Generics also define important compatibility rules. List<Integer> is not a subtype of List<Number>, because that would allow inserting a Double into an integer list. Wildcards express controlled flexibility: ? extends Number supports reading elements as Number, while ? super Integer supports adding integers. Type arguments must be reference types, so use Integer rather than int. Interviewers usually assess whether you can explain these safety guarantees, apply bounds and wildcards, and recognise erasure-related limitations instead of merely recalling angle-bracket syntax.
Key points to remember
- Generics provide compile-time type safety and reusable classes, interfaces, and methods.
- Box<String> restricts set() to strings and gives get() the compile-time return type String.
- Type erasure replaces type parameters with their upper bounds or Object; parameterised instances share a runtime class.
- Generic declaration metadata can be inspected through reflection, but ordinary instances do not expose their supplied type arguments.
- Generic types are invariant; bounded wildcards provide controlled flexibility, and primitive type arguments are not allowed.
Common mistakes candidates make
- Using raw types such as List instead of List<String>, weakening compile-time checks and risking ClassCastException.
- Assuming List<Integer> can be assigned to List<Number> because Integer extends Number.
- Claiming all generic information disappears at runtime, ignoring generic signatures retained on declarations.
Likely follow-up questions
What is the difference between ? extends T and ? super T?
Use ? extends T when reading values as T; you generally cannot add non-null values because the exact element type is unknown. Use ? super T when adding T values, but retrieved elements are safely typed only as Object. Remember PECS: Producer Extends, Consumer Super.
Why can you not write new T() or use instanceof List<String>?
Erasure means T does not identify a concrete runtime class that Java can instantiate with new T(). Similarly, runtime checks cannot distinguish List<String> from List<Integer>, so use instanceof List<?> to test whether an object is a list.
What is a bounded type parameter?
A bound restricts which types may replace a type parameter, such as <T extends Number>. It also lets the generic implementation use methods declared by the bound, for example calling doubleValue() on a T value.
Interview tip: Start with compile-time type safety, demonstrate Box<String>, then explain erasure with the reflection caveat.
