Chapter 4 Flashcards — Generics
flashcards effective-java generics
What is a raw type and why should you never use one in new code?
?
A raw type is a generic type used without its type parameter (e.g., List instead of List<String>). Raw types exist only for pre-Java-5 compatibility. Using them loses all compile-time type safety — errors become ClassCastException at runtime instead of compile errors. Use List<?> (unbounded wildcard) if you genuinely don’t know the type.
When is it legitimate to use a raw type (name the two exceptions)?
?
- Class literals:
List.classis legal;List<String>.classis a syntax error — type parameters cannot appear in class literals. instanceof: Because generics are erased at runtime,o instanceof List<String>is illegal. Useo instanceof List(raw type), then immediately cast toList<?>(wildcard, not raw type):List<?> list = (List<?>) o;.
What is type erasure and what does it mean at runtime?
?
Type erasure is the process by which the compiler removes all generic type information from bytecode. At runtime, List<String>, List<Integer>, and List are all represented as List. The compiler inserts casts where values are extracted from generic containers. Consequences: you cannot use instanceof with parameterized types, cannot create generic arrays, and cannot overload methods that differ only in type parameters.
What are the five most common unchecked warning categories in Java generics?
?
- Unchecked cast:
(List<String>) obj - Unchecked method invocation: calling a raw-type method with generic arguments
- Unchecked conversion: assigning a raw type to a parameterized type (
List list = new ArrayList(); List<String> s = list;) - Unchecked vararg creation: generic varargs parameter (see Item 32)
- Unchecked generic array creation:
new List<String>[10]— actually illegal, but similar patterns arise
What is the correct procedure when you cannot eliminate an unchecked warning?
?
- Verify the cast is provably safe by reasoning about the code.
- Apply
@SuppressWarnings("unchecked")to the smallest possible scope (prefer a local variable declaration over a method; never a class). - Add a comment explaining why the cast is safe. Without the comment, the suppression is worse than the warning — it silences real bugs in the future.
What is the difference between covariance and invariance in Java’s type system?
?
Arrays are covariant: if Dog extends Animal, then Dog[] is a subtype of Animal[]. This allows runtime ArrayStoreException bugs. Generics are invariant: List<Dog> is NOT a subtype of List<Animal>, even though Dog extends Animal. This invariance prevents the array bug — you cannot assign a List<Dog> to a List<Animal> reference and then add a Cat. Wildcards (? extends Animal) provide a safe, read-only form of covariance for generics.
Why can’t you create a generic array (e.g., new E[])?
?
Because combining array covariance (reification) with generic erasure creates a type-safety hole. If new E[] were allowed, you could upcast it to Object[] (via array covariance), store the wrong type (no ArrayStoreException because the runtime sees only Object[]), and get a ClassCastException far from the bug’s source. The compiler prevents this at the point of creation. Alternatives: use List<E>, or cast new Object[] to E[] in a private context with @SuppressWarnings.
State the PECS rule and give an example for each part.
?
PECS: Producer Extends, Consumer Super.
- Use
? extends Twhen the parameter produces T values (you read from it):void pushAll(Iterable<? extends E> src)— src produces E for the stack. - Use
? super Twhen the parameter consumes T values (you write to it):void popAll(Collection<? super E> dst)— dst consumes E from the stack. - Use no wildcard when the parameter does both (read and write).
- Use
?(unbounded) when the type doesn’t matter at all.
Should wildcards appear in method return types? Why or why not?
?
No. A wildcard return type forces callers to use wildcards in their code, making the API harder to use. If a method returns List<? extends Number>, the caller cannot add anything to the result. Instead, use a concrete type parameter in the return type: <T extends Number> List<T> getNumbers(). Wildcards belong in method parameters, not return types.
What is the helper method pattern for wildcards and when do you use it?
?
When a wildcard in a public method makes the implementation impossible to write (e.g., you need to call list.set() on a List<?>), delegate to a private generic helper that captures the wildcard type:
public static void swap(List<?> list, int i, int j) {
swapHelper(list, i, j);
}
private static <E> void swapHelper(List<E> list, int i, int j) {
list.set(i, list.set(j, list.get(i))); // E is captured, set() works
}The public API stays flexible (List<?>), the private helper does the actual type-safe work.
What is heap pollution and what is its primary cause in modern Java code?
?
Heap pollution is when a variable of a parameterized type refers to an object that is not of that type. It is silent at the point of pollution — the ClassCastException occurs later when a value is extracted. Primary modern cause: generic varargs methods. When you call a method with List<String>..., the JVM creates a List[] array (erased), which can be written to with List<Integer> via array covariance, causing heap pollution. This is why the compiler warns on calls to generic varargs methods.
What conditions must hold for @SafeVarargs to be correctly applied?
?
@SafeVarargs suppresses the caller-side heap pollution warning for a generic varargs method. It is correct only when the method:
- Never stores anything into the varargs array (no
arr[i] = ...). - Never exposes the varargs array to untrusted code (doesn’t pass it to another method that could store into it).
The annotation can only be placed on methods that cannot be overridden:static,final, orprivate. Applying it to an overridable method could suppress warnings for unsafe overrides.
What is a bounded type parameter and how does it differ from a bounded wildcard?
?
Bounded type parameter (<T extends Comparable<T>>): declares a named type variable in a method/class header. T is fixed for the duration of the call/instance, can be used in multiple parameter types, and can have values written back through it. Bounded wildcard (List<? extends Comparable<?>>): an anonymous unknown type in a parameter position. The type cannot be named or used as a return type component. You cannot add elements to a List<? extends T>. Use bounded type parameters when you need to refer to the type in multiple places or in the return type; use wildcards for flexibility in parameters you only read from.
What is a recursive type bound and what is it used for?
?
A recursive type bound is a type parameter bounded by an expression involving itself: <T extends Comparable<T>>. It means “a type T that can be compared to itself” — i.e., T has a natural ordering. This is the standard way to write a generic max() or min() method that works for any Comparable type. Extended form for more flexibility: <T extends Comparable<? super T>> — T can be compared by any supertype’s Comparable, needed for types like ScheduledFuture that inherit Comparable<Delayed>.
What is the typesafe heterogeneous container pattern?
?
A design where the key is parameterized (Class<T>) rather than the container. The container is Map<Class<?>, Object>. The getFavorite method uses type.cast(map.get(type)) — a dynamic (runtime-checked) cast — to return a properly typed value. This allows storing values of many different types in one container with full type safety per slot. Used in the JDK for AnnotatedElement.getAnnotation(Class<T>).
How does Class.cast() differ from a Java cast expression?
?
A Java cast expression like (String) obj uses erasure — the compiler inserts a checkcast bytecode instruction that fails with a ClassCastException at the cast site. Class.cast(obj) is a method call that also performs a runtime type check (via Class.isInstance()), but it uses the Class<T> token to express the target type explicitly. The key advantage: Class.cast() preserves the generic type T, allowing it to work in generic context (e.g., public <T> T getFavorite(Class<T> type) { return type.cast(map.get(type)); }). A plain cast (T) would be an unchecked cast due to erasure.
What is the limitation of the typesafe heterogeneous container with generic types like List<String>?
?
List<String>.class is a syntax error — it doesn’t exist. List<String> and List<Integer> erase to the same class object: List.class. This means you cannot have separate slots for List<String> and List<Integer> in the container — they would map to the same key. The workaround is Guava’s TypeToken library, which uses super type tokens (anonymous subclasses of TypeToken<List<String>>) to capture and distinguish generic types at runtime.
What does asSubclass() do and when do you use it in generic code?
?
Class.asSubclass(Class<U> clazz) casts the receiver Class object to Class<? extends U>, performing the check at runtime. Returns the same Class object but with a narrower type, or throws ClassCastException if the class does not extend U. Used when you have a Class<?> from a string lookup (e.g., Class.forName(name)) and need to use it as a Class<? extends SomeBaseType> — you cannot do this with a compile-time cast (erasure), but asSubclass is a safe runtime conversion.
Why is List<String> not a subtype of List<Object> even though String extends Object?
?
Because of invariance: if List<String> were a subtype of List<Object>, you could do: List<Object> ol = (List<Object>) stringList; ol.add(42); — adding an Integer to what is actually a List<String>, corrupting it. The compiler prevents this assignment entirely. Arrays DO allow this substitution (String[] is-a Object[]), but it fails with ArrayStoreException at runtime — a design flaw. Generics enforce the restriction at compile time via invariance.
What is the generic singleton factory pattern and when is it useful?
?
A generic singleton factory creates a single instance of a stateless object and casts it to any generic type on demand. Used for stateless function objects (like identity functions, natural comparators) where the same instance can safely serve all types. Example: private static final UnaryOperator<Object> IDENTITY = t -> t; @SuppressWarnings("unchecked") public static <T> UnaryOperator<T> identity() { return (UnaryOperator<T>) IDENTITY; }. The cast is safe because the function never inspects or modifies its argument.
What does List.of() (Java 9+) replace in the context of generics?
?
List.of() replaces varargs-based generic list creation patterns like Arrays.asList(). It creates an immutable list, is null-hostile, and is properly typed without unchecked casts. It also replaces many use cases for generic varargs methods that accepted T... arguments, since callers can now wrap their arguments in List.of(...) instead. List.of() itself uses @SafeVarargs internally in the JDK implementation.
Can you overload two methods that differ only in their type parameter (e.g., process(List<String>) and process(List<Integer>))?
?
No. Due to type erasure, both methods erase to process(List) — the same signature after erasure. The compiler rejects this as a duplicate method declaration. This is called an erasure collision. The workaround is to use different method names, or use a single method with a wildcard parameter and perform dispatch inside the method.
What is the var keyword’s relationship to generics (Java 10+)?
?
var (Java 10+) uses local variable type inference — the compiler infers the full generic type from the initializer. This reduces verbosity without losing type safety: var map = new HashMap<String, List<Integer>>(); is equivalent to HashMap<String, List<Integer>> map = new HashMap<>(). var is strictly a compile-time feature — it infers the most specific type, which is important: var list = List.of("a","b") infers List<String>, not List<Object>.
What is the difference between Set<?> (unbounded wildcard) and Set (raw type)?
?
Both allow holding a Set of any element type, but they differ critically in safety: Set<?> is type-safe — you cannot add anything (except null) to a Set<?> because the compiler doesn’t know the element type. Set (raw type) is unsafe — you can add anything to it, bypassing the type system and risking ClassCastException later. When you need a Set of unknown type for read-only access, always use Set<?>.
What happens when you mix generic varargs with @SafeVarargs in a subclass?
?
@SafeVarargs only suppresses warnings for the annotated method itself. If that method is overridable (not static, final, or private) and a subclass overrides it, the override might be unsafe. This is why @SafeVarargs can only be applied to non-overridable methods — the JVM has no way to guarantee subclass implementations are safe. If you need a safe generic varargs method in an inheritable class, make it final or extract the logic into a private static helper.
What is the “prefer lists to arrays” rule’s real-world impact on performance?
?
The boxing/unboxing overhead of List<Integer> vs int[] is real but often overstated. For typical business logic, the difference is negligible. For high-performance numerical algorithms (hundreds of millions of operations), use primitive arrays. For general-purpose generic data structures (stacks, queues, graphs, trees), the type safety and simplicity of List<E> outweigh the minor performance cost. The JVM’s JIT compiler also optimizes ArrayList operations heavily, closing much of the gap.
What are bounded wildcards in return types called, and why should you avoid them?
?
Wildcards in return types force wildcard pollution on callers — the caller’s code must also use wildcards to interact with the returned value. For example, if a method returns List<? extends Number>, the caller gets back a list they can only read from, and every downstream variable holding it must also be typed as List<? extends Number>. This makes the API harder to use without making it more flexible. Use a concrete type parameter in the return type instead.
How does the Collections.checkedList() wrapper help with generics safety?
?
Collections.checkedList(list, String.class) returns a dynamically type-checked view of a list — any attempt to insert an element of the wrong type throws a ClassCastException immediately at the add() call, not somewhere downstream. This is useful when you pass a generic list to legacy (raw-type) code that might insert wrong-typed elements — the fail-fast behavior makes the bug location obvious rather than hiding it until someone reads the wrong element.
What is Collections.emptyList() vs List.of() for generic empty lists?
?
Collections.emptyList() returns a type-inferred, immutable, singleton empty list. It uses the generic singleton factory pattern — a single instance cast to List<T> on demand. List.of() (Java 9+) with no arguments is effectively equivalent. Both are null-safe, immutable, and properly typed. Prefer List.of() in new code (Java 9+) for consistency. Collections.emptyList() is idiomatic in pre-Java 9 codebases.
What does Class<T> enable that Object alone cannot provide?
?
Class<T> is a type token — it carries the type information T as a runtime value (not just a compile-time constraint). This enables:
- Dynamic casting via
type.cast(obj)— a checked cast that returnsT. - Type-safe heterogeneous containers — storing and retrieving typed values with
Class<T>as the key. - Reflection —
type.newInstance(),type.isInstance(), etc. - Annotation retrieval —
element.getAnnotation(MyAnnotation.class).
AnObjectparameter loses the type — you’d need an unchecked cast.Class<T>preserves it through the generic type system.
Total Cards: 31
Review Time: ~30 minutes
Priority: HIGH
Last Updated: 2026-05-10