Java Generics and Reflection for Strong Senior Developers
How does type erasure work in Java generics, and what are its implications?
Type erasure is the process by which Java removes generic type information at runtime.
Implications:
Generic types are converted to their raw types (e.g., List<T> becomes List).
Type information is not available at runtime — no instanceof checks or new T().
Overloading methods based on generic type parameters is not allowed.
Workarounds:
Pass Class<T> or TypeToken if runtime type information is needed.
Use reflection with parameterized types where possible.
What are the differences between <?>, <? extends T>, and <? super T> in
generics?
<?> (unbounded wildcard):
Represents an unknown type — useful for read-only access.
<? extends T> (covariant):
Accepts T or any subtype of T.
Safe for reading elements as type T.
Cannot add elements except null.
<? super T> (contravariant):
Accepts T or any supertype of T.
Safe for adding elements of type T.
Reading returns Object.
Guideline:
Use PECS — Producer Extends, Consumer Super.
How would you implement a type-safe generic method in Java?
Generic methods use type parameters to ensure compile-time type safety.
Example:
public <T> T pickRandom(List<T> items)
{ return items.get(new Random().nextInt(items.size())); }
Benefits:
Avoids casting and ClassCastException at runtime.
Allows reusability of logic across types.
Can use multiple bounds:
public <T extends Number & Comparable<T>> T max(T a, T b)
How does Java Reflection allow dynamic class loading and inspection?
Reflection provides APIs to inspect and manipulate classes, fields, methods, and
constructors at runtime.
Key capabilities:
Load class dynamically with Class.forName().
Inspect fields/methods with getDeclaredFields(), getMethods().
Instantiate objects with Constructor.newInstance().
Modify private members by setting setAccessible(true).
Use cases:
Frameworks (e.g., Spring, Hibernate) for dependency injection, ORM, serialization.
Risks:
Breaks encapsulation, harder to maintain, performance overhead.
How would you combine generics and reflection safely in real-world code?
Challenges: generics are erased at runtime, but reflection operates at runtime.
Strategies:
Use Class<T> to retain type information (e.g., public <T> T parse(String json, Class<T>
type)).
Use ParameterizedType to reflect generic types from fields/methods.
Use libraries like Gson, Jackson, or TypeToken for complex generic reflection (e.g.,
List<String>).
Example:
Type listType = new TypeToken<List<String>>() {}.getType();
Avoid casting without validation — use instanceof, Class#isAssignableFrom, or
annotations to enforce constraints.