Generics and Collections practice questions

From OCP Java SE 21 (1Z0-830) · 21 questions on this topic

Generics and Collections practice questions from OCP Java SE 21 (1Z0-830). This pack has 21 questions tagged Generics and Collections, drawn from its timed mock exams. 8 of them are worked through in full below — the question, every option, why each is right or wrong, and the explanation.

Worked examples for Generics and Collections

  1. Question 1

    A helper method takes a raw List and stores an Object into it. What is the result of compiling and running this program? ```java import java.util.*; public class Main { static void addAny(List raw, Object value) { raw.add(value); } public static void main(String[] args) { List<String> labels = new ArrayList<>(); labels.add("ok"); addAny(labels, 42); String s = labels.get(1); System.out.println(s.length()); } } ```

    1. A. 2

      Assumes the stored value comes back as the string "42" of length 2; no conversion happens, the element is really an Integer, so the retrieval cast fails before any length is taken.

    2. B. Compilation fails

      Passing a parameterized list to a raw-type parameter produces only an unchecked warning, not a compile error, so the program compiles.

    3. C. Throws ArrayStoreException

      ArrayStoreException signals an array-covariance store failure; this is a generics-erasure failure, which surfaces as a ClassCastException on retrieval instead.

    4. D. Throws ClassCastExceptionCorrect answer

      The raw parameter lets an Integer be stored in a list typed for strings (heap pollution); on retrieval the compiler's synthetic cast to String fails at run time with ClassCastException.

    Explanation

    Calling through a raw-type parameter defeats generic type checking with only an unchecked warning, allowing an element of the wrong type to be stored, which is heap pollution. Because generics are erased, the fault is not detected at the store; it appears when the element is read back and the compiler-inserted cast to the declared element type fails at run time.

  2. Question 2

    What does this print? ```java import java.util.*; public class Main { public static void main(String[] args) { List<Integer> src = new ArrayList<>(List.of(1, 2)); List<Integer> copy = List.copyOf(src); src.add(3); System.out.println(src + " " + copy); } } ```

    1. A. [1, 2, 3] [1, 2]Correct answer

      List.copyOf takes an unmodifiable snapshot of src's current elements [1, 2]; the later src.add(3) changes only src, so copy stays [1, 2] (Javadoc 21 — List.copyOf).

    2. B. [1, 2, 3] [1, 2, 3]

      This would require copyOf to return a live view of the source; it copies the elements instead, so it does not reflect later changes to src.

    3. C. Throws UnsupportedOperationException

      The add targets src, an ordinary mutable ArrayList, so no exception occurs; only mutating the unmodifiable copy would throw UnsupportedOperationException.

    4. D. [1, 2] [1, 2]

      This misses that src.add(3) succeeds on the mutable original, leaving src as [1, 2, 3].

    Explanation

    List.copyOf produces an unmodifiable snapshot rather than a live view — it captures the source's elements at the moment of the call and is unaffected by later changes to the original. Mutating the still-mutable source afterward updates only the source, while the copy retains exactly the elements it captured. The trap is treating the copy as a view; it never reflects later modifications.

  3. Question 3

    What is the output of the following program? ```java import java.util.ArrayList; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6)); nums.removeIf(n -> n % 2 == 0); System.out.println(nums); } } ```

    1. A. [2, 4, 6]

      Reverses the semantics: these are the elements the predicate matched and removed, not the ones that survive. removeIf retains the elements for which the predicate returned false, discarding those for which it returned true.

    2. B. [1, 3, 5]Correct answer

      removeIf removes every element for which the predicate returns true. The predicate n -> n % 2 == 0 is true for 2, 4, and 6, so those three are removed and the odd elements remain (Collection.removeIf Javadoc).

    3. C. [1, 2, 3, 4, 5, 6]

      Assumes removeIf has no effect. ArrayList is a mutable list that fully supports removeIf; matching elements are removed in place, so the list is shorter after the call.

    4. D. Throws UnsupportedOperationException

      UnsupportedOperationException is thrown by unmodifiable lists (such as the List.of result itself). The code copies that result into a new ArrayList with new ArrayList<>(...), producing a fully mutable list that supports all mutation operations including removeIf.

    Explanation

    Collection.removeIf(Predicate) iterates the collection and removes every element for which the predicate evaluates to true, modifying the collection in place. The lambda n -> n % 2 == 0 is true for the even values 2, 4, and 6, so those are removed; the odd values 1, 3, and 5 remain. The enclosing ArrayList is a mutable list — new ArrayList<>(List.of(...)) produces a modifiable copy, so no exception is thrown. The elements the predicate matched are the ones discarded, not the ones the list contains afterward.

  4. Question 4

    remove(1) is called on a List<Integer>. What is printed? ```java import java.util.*; public class Main { public static void main(String[] args) { List<Integer> codes = new ArrayList<>(List.of(10, 20, 30)); codes.remove(1); System.out.println(codes); } } ```

    1. A. [10, 20, 30]

      Assumes the value 1 is searched for as an element and not found; that would require the boxed object overload, but an unboxed int literal selects the index-based remove.

    2. B. [20, 30]

      Assumes 1-based indexing so index 1 is the first element; list indices are zero-based, so index 1 is the second element (value 20).

    3. C. [10, 30]Correct answer

      The int literal selects the index-based remove because overload resolution prefers the match needing no boxing, removing the element at index 1 (value 20) and leaving [10, 30].

    4. D. Compilation fails

      The call is unambiguous, since an int argument resolves to the index-based remove, so it compiles.

    Explanation

    List declares both an index-based remove and an object-based remove, and overload resolution prefers the applicable candidate that requires no autoboxing, so an int literal binds to the index-based overload. That removes the element at the given zero-based position rather than searching for a matching value, which would require passing a boxed argument.

  5. Question 5

    What is the output of the following program? ```java import java.util.*; public class Main { public static void main(String[] args) { List<String> names = new ArrayList<>(List.of("Carol", "Alice", "Bob", "Dave")); names.sort(Comparator.comparingInt(String::length) .thenComparing(Comparator.naturalOrder()) .reversed()); System.out.println(String.join(",", names)); } } ```

    1. A. Bob,Dave,Alice,Carol

      This is the output of the comparator without `.reversed()` at all: length ascending (Bob=3, Dave=4) then natural order ascending within equal lengths (Alice before Carol because 'A' < 'C'). The `.reversed()` call is simply ignored in this reasoning.

    2. B. Bob,Dave,Carol,Alice

      This result treats `.reversed()` as applying only to the secondary `thenComparing` key, leaving the primary length key ascending. It gives Bob(3), Dave(4) first, then Carol before Alice because the natural order is reversed within length 5. In reality `.reversed()` inverts the entire composed comparator, flipping both keys simultaneously.

    3. C. Carol,Alice,Dave,BobCorrect answer

      `.reversed()` is called on the entire composed `Comparator`, inverting both the primary key (length, now descending) and the secondary key (natural order, now reverse-alphabetical) together. The 5-character strings come first; among them, 'C' > 'A' in descending alphabetical order so Carol precedes Alice. Dave (4 characters) follows, and Bob (3 characters) is last (Comparator.reversed() Javadoc, Java SE 21).

    4. D. Alice,Carol,Dave,Bob

      This result treats `.reversed()` as inverting only the primary (length) key while leaving the secondary key as natural ascending order. Length becomes descending so the 5-character strings lead, but 'Alice' comes before 'Carol' because alphabetical order is treated as unchanged. The actual API reverses all keys of the composed comparator as a unit.

    Explanation

    `Comparator.reversed()` returns a comparator that imposes the reverse ordering of its entire receiver — it does not selectively negate only the last key added to a chain. A comparator built with `comparingInt(...).thenComparing(...)` is a single composed object; calling `.reversed()` on it flips both the length key (ascending → descending) and the natural-order key (alphabetical ascending → reverse-alphabetical) simultaneously. Leaving only the secondary key reversed while the primary stays ascending produces a different ordering (Bob, Dave first). Ignoring the reversal entirely keeps both keys ascending. Reversing only the primary key while the secondary stays alphabetically ascending is a third distinct wrong result. Only treating the reversal as applying to the whole composed comparator yields the correct output.

  6. Question 6

    A method takes a List with a lower-bounded wildcard and writes into it. What does this print? ```java import java.util.*; public class Main { static void fill(List<? super Integer> sink) { sink.add(1); sink.add(2); } public static void main(String[] args) { List<Number> nums = new ArrayList<>(); fill(nums); Object first = nums.get(0); System.out.println(nums + " " + first); } } ```

    1. A. [1, 2] 1Correct answer

      ? super Integer is the consumer side of PECS, so adding an Integer is always legal; the writes mutate the caller's nums, and reading the boxed Integer 1 back and printing it invokes Integer's toString, giving 1.

    2. B. Compilation fails because add(...) cannot be called on a List<? super Integer>

      Applies the ? extends rule to a ? super wildcard. It is the producer form, List<? extends Number>, that refuses add; lower-bounded lists accept writes, which is their whole purpose.

    3. C. [1, 2] java.lang.Integer@1b6d3586

      Assumes that declaring the variable as Object makes println call Object.toString. The declared type selects the method signature, but the dynamic type selects the implementation; the object is an Integer, which overrides toString, so 1 prints.

    4. D. Compilation fails because List<Number> is not a subtype of List<? super Integer>

      Reads the wildcard backwards. List<? super Integer> accepts List<Integer>, List<Number>, List<Serializable> and List<Object>, so List<Number> is a legal argument.

    Explanation

    Trace: `? super Integer` is the CONSUMER side of PECS — whatever the wildcard captures, it is some supertype of Integer, so an Integer is always a legal element to add. `sink.add(1)` and `sink.add(2)` compile and mutate the caller's list, which is the same object as `nums`. `List<Number>` matches `List<? super Integer>` because Number is a supertype of Integer. Reading back, the only thing the compiler can promise about an element of a `? super Integer` list is that it is an Object, but here we read through `nums`, typed `List<Number>`, and assign to an Object anyway. `println` on the list gives [1, 2], and `first` holds the boxed Integer 1, whose toString gives 1. Why the others are wrong: `Compilation fails because add(...) cannot be called...` applies the `? extends` rule to a `? super` wildcard. It is the PRODUCER form, `List<? extends Number>`, that refuses add — the compiler cannot know which subtype the list actually holds. Lower-bounded lists accept writes; that is the whole point of them. `Compilation fails because List<Number> is not a subtype...` reads the wildcard backwards. `List<? super Integer>` accepts `List<Integer>`, `List<Number>`, `List<Serializable>` and `List<Object>` — every list whose element type is Integer or above. `[1, 2] java.lang.Integer@1b6d3586` assumes that declaring the variable as Object makes `println` call `Object.toString()`. The declared type selects the method signature, but the DYNAMIC type selects the implementation: the object is an Integer and Integer overrides toString, so 1 is printed. Exam tip: PECS — Producer Extends, Consumer Super. `? extends T` lets you read a T but not write (except null); `? super T` lets you write a T but only read back an Object. The reverse trap of this question is a method declared `List<? extends Number>` whose body calls add — that one really does fail to compile.

  7. Question 7

    What is the output of the following program? ```java import java.util.ArrayDeque; import java.util.Deque; public class Main { public static void main(String[] args) { Deque<Integer> stack = new ArrayDeque<>(); stack.push(1); stack.push(2); stack.push(3); System.out.println(stack.pop() + " " + stack.peek()); } } ```

    1. A. 3 2Correct answer

      push(e) is addFirst(e), so the three pushes build the head-to-tail sequence [3, 2, 1]. pop() is removeFirst(), returning 3 and leaving [2, 1]. peek() is peekFirst(), returning 2 without removing it (Deque Javadoc).

    2. B. 1 2

      Applies FIFO semantics: assumes pop() returns the first element pushed (1). Deque.push() is specified as addFirst() and pop() as removeFirst(), so the most recently pushed element is retrieved first — LIFO, not FIFO.

    3. C. 3 3

      Treats peek() as re-reading the element that pop() just removed. pop() removes 3 from the head, making 2 the new head; peek() reads that new head and returns 2, not 3.

    4. D. 1 3

      Inverts both operations simultaneously: pop() would return the first element pushed (1) and peek() the last (3). Neither matches the Deque specification; push/pop are LIFO and peek reads the same end as pop — the head, not the tail.

    Explanation

    Deque.push(e) is specified as addFirst(e), inserting at the head of the deque. Three successive pushes of 1, 2, then 3 build a head-to-tail sequence of [3, 2, 1]. Deque.pop() is removeFirst(): it retrieves and removes the head element (3), leaving [2, 1]. Deque.peek() is peekFirst(): it retrieves the new head (2) without removing it. FIFO retrieval — returning the first element inserted — is the contract of poll() and remove(), not push() and pop(), which implement LIFO stack semantics on the same underlying deque.

  8. Question 8

    Two overloads of total() differ only in their wildcard bound — one is a producer, the other a consumer. What happens when this program is compiled and run? ```java import java.util.*; public class Main { static int total(List<? extends Number> nums) { int t = 0; for (Number n : nums) { t += n.intValue(); } return t; } static int total(List<? super Integer> nums) { return nums.size(); } public static void main(String[] args) { System.out.println(total(List.of(1, 2, 3))); } } ```

    1. A. 6

      Assumes the producer overload (bounded by extends) is chosen and sums the values; the two overloads never coexist because they share one erasure, so the class does not compile.

    2. B. Compilation failsCorrect answer

      Both wildcard-typed parameters erase to plain List, giving the two methods the same erasure, a name clash the compiler rejects at the second declaration.

    3. C. 3

      Assumes the consumer overload (bounded by super) is chosen and returns the size; wildcard bounds cannot distinguish overloads because erasure discards them.

    4. D. Throws ClassCastException

      Assumes a runtime cast failure; the erasure conflict is a compile-time error, so no code runs.

    Explanation

    Two methods in the same class may not have signatures that are identical after type erasure, and wildcard bounds are compile-time-only information that erasure discards. Two overloads whose parameters differ only by their wildcard bound therefore collapse to the same erased signature, which is a compile-time name clash detected at the declaration rather than at any call site.

Practise all 21 Generics and Collections questions

OCP Java SE 21 has the full set, inside timed mock exams that mirror real exam conditions — every question with a worked explanation.

Open OCP Java SE 21

Other topics in this pack