- Which statement correctly describes the result?
Answer: It prints [1, 2] because final prevents reassignment, not mutation
When you see final applied to a reference variable in Java, the key question to ask is: *what exactly is final protecting?* The answer is the reference itself, not the object it points to.
Here, final List<Integer> first means you cannot reassign first to point to a different list — but the underlying ArrayList object is still fully mutable. When you write List<Integer> second = first, you're not copying the list; you're copying the reference. Both first and second now point to the exact same ArrayList in memory. So when second.add(2) runs, it modifies that shared object, and first reflects the change because it's looking at the same list. The output is [1, 2], confirming B is correct.
A is wrong because it conflates "can't reassign the variable" with "can't mutate the object." final offers no protection against calling .add(), .remove(), or any other mutating method on the list. D shares this same misconception — the code compiles and runs without issue. C is wrong because assignment between reference variables never creates a copy in Java. second = first does not invoke any copy constructor or cloning; it simply gives second the same memory address that first holds.
A useful rule of thumb: in Java, final locks the variable's binding, not the object's state. If you want an immutable list, you'd need Collections.unmodifiableList() or List.of(). Watch for exam questions that bait you into thinking final = immutable — it's one of the most common traps in Java fundamentals.
- What is printed when this code is executed?
Answer: [z, y]:true, because the view reflects base but rejects its own mutation
When you see Collections.unmodifiableList() in Java, the key concept to understand is that it creates a view — not a copy. The unmodifiable wrapper sits on top of the original list and delegates read operations to it, while blocking any write operations attempted through the wrapper itself.
Here's what happens step by step: base is a mutable ArrayList. view wraps base but shares the same underlying data. When base.set(0, "z") is called, it mutates the backing list directly — view immediately reflects this change because they share storage. Then view.add("q") throws UnsupportedOperationException because the wrapper intercepts write calls and rejects them, setting failed = true. The final output is [z, y]:true, making D correct.
A is wrong because it assumes unmodifiableList creates an independent snapshot, like copying the data. It doesn't — it's a live view. Changes to base are always visible through view.
B is wrong on both counts: view does not permit structural modifications (it throws), and the output would never include "q" since that add fails.
C is wrong because it inverts the isolation logic. Modifications through base are not isolated from view — they propagate through. Only modifications attempted through view are blocked.
Strategy tip: Whenever you see Collections.unmodifiableList(), remember the mantra: read-through, write-blocked. Mutations via the original reference always propagate; mutations via the wrapper always throw. This pattern also applies to unmodifiableMap() and unmodifiableSet().
- What is printed, and what does it reveal about the attempted immutability of
Sample? Answer: [2, 7]; the constructor copies input, but the accessor exposes state
When designing immutable or defensively-copied classes in Java, you need to trace every path where internal state can be read or written — both going in (constructor) and going out (accessor methods).
Here, the constructor does its job correctly: values.clone() creates a fresh copy of the input array, so when source[0] = 9 runs, the internal values array is unaffected — it still holds [2, 4]. That rules out any answer claiming the first element becomes 9.
The vulnerability is in the values() method, which returns the internal array directly — no copy, no protection. When you call sample.values() and assign it to exposed, you're holding a direct reference to the private array. So exposed[1] = 7 mutates the actual internal state. When sample.values() is called again for printing, it returns that same modified array: [2, 7]. This confirms B is correct — the constructor protects against external mutation, but the accessor betrays it.
A is wrong because it assumes both barriers hold. The accessor does not prevent mutation — returning a reference to a private array is not protection. C is wrong on both counts: the constructor does clone (so index 0 stays 2), and the accessor does not copy (so index 1*does* change). D is wrong because the constructor's clone successfully isolates the internal array from source, meaning source[0] = 9 has no effect inside Sample.
A useful rule of thumb: clone on the way in, clone on the way out. If either side is missing, your encapsulation has a leak.
- What is printed after
edit returns? Answer: XA, because the first append mutates the caller's object but reassignment is local
When you see a question about method parameters in Java, the key concept to recall is pass-by-value — and critically, what that means for object references.
In Java, every argument is passed by value. When you pass an object, you're passing a copy of the reference (the memory address), not the object itself. This means the method can use its local copy of that reference to mutate the original object — but if the method reassigns its parameter to a new object, the caller's reference is completely unaffected.
Tracing through edit: when text.append("A") is called, both text and value still point to the same StringBuilder, so the original object becomes "XA". Then text = new StringBuilder("B") creates a brand-new object and points the local text variable at it — value still points to "XA". The subsequent text.append("C") mutates that new, unreachable object. When edit returns, value prints XA, confirming A is correct.
B is wrong because it assumes reassignment doesn't break the shared reference — it does. After text = new StringBuilder("B"), text and value no longer point to the same object. C is wrong because it assumes that reassigning the parameter also changes the caller's variable, which would only be true if Java passed references by reference (it doesn't). D is wrong in the opposite direction — it overcorrects by claiming no mutation ever reaches the caller, but the first append clearly does modify the shared object.
Your study tip: always distinguish between mutating the object a reference points to (affects the caller) versus reassigning the reference itself (local only, never affects the caller).
- What is printed when the expression is evaluated?
Answer: 9:6, because the first call changes the argument used by the second call
When you see an expression like Counter.bump(2) + Counter.bump(Counter.total), the critical concept being tested is Java's left-to-right evaluation order for operands and arguments. Java guarantees that in a binary expression, the left operand is fully evaluated before the right operand begins.
Here's the step-by-step execution that makes A correct: First, Counter.bump(2) is called. It adds 2 to total (which starts at 1), making total = 3, and returns 3. Now Java evaluates the right operand — but before calling bump, it must evaluate its argument: Counter.total, which is currently 3. So the second call becomes Counter.bump(3). That adds 3 to total, making total = 6, and returns 6. The final result is 3 + 6 = 9, and Counter.total is 6. So 9:6 is printed, confirming A.
B is wrong because it assumes both calls see total = 1, as if arguments are captured before any side effects run. That's not how Java works — the left call executes completely first, mutating total before the right argument is read.
C is wrong in its arithmetic. It seems to assume the second call returns its argument (3) rather than the updated total (6), giving 3 + 3 = 7. But bump returns total after the addition, not amount.
D is wrong because it imagines both operands "see" the final value 6 — as if evaluation happens after all mutations are complete. That reverses Java's actual sequencing.
Your study tip: whenever you see method calls with side effects in a single expression, always trace left to right, updating shared state after each call before moving to the next operand.
- What is printed when this code is executed?
Answer: abc:ab, because each string operation produces a new string
Whenever you see a question about String operations in Java, the first thing to remember is that Strings are immutable — once created, a String object can never be changed. Every method that appears to "modify" a String actually returns a new String object, leaving the original untouched.
Walking through the code: s and t both start pointing to the same "ab" object. When s.concat("c") is called, it creates a brand-new "abc" object and assigns it back to s. Now s points to "abc" and t still points to the original "ab". Then t.replace('a', 'z') is called — this also creates a new String ("zb"), but crucially, that result is never assigned to anything. It's immediately discarded. So t remains pointing to "ab". The final print gives you abc:ab, confirming C is correct.
Choice A is wrong because it assumes String methods mutate the original object — they never do. Choice B is wrong because while s and t do initially reference the same object, s is reassigned when concat returns a new object, so they diverge. Choice D is wrong in a tricky way — it inverts the logic. The concat result is captured (assigned back to s), while the replace result is the one that's discarded, not the other way around.
A reliable strategy: whenever you see a String method call, ask yourself, "Is the return value being saved?" If not, the original variable is unchanged. This pattern appears constantly on Java exams.
- Which statement best describes the side effect of changing
key after it has been inserted? Answer: Lookup becomes unreliable because the key may now be searched in another hash bucket
Whenever you see a question involving HashMap and mutable keys, the critical concept to recall is how hash-based collections locate entries: they first compute the key's hash code to find the correct bucket, then use equals() to find the exact entry within that bucket.
When key with id = 10 is inserted, the map stores it in the bucket corresponding to hashCode(10). After calling key.setId(20), the same object now produces hashCode(20) — a completely different value. When you later call map.get(key), the map computes hashCode(20), looks in the wrong bucket, finds nothing there, and returns null — even though the entry still physically exists somewhere in the map. This is why C is correct: the key is being searched in the wrong bucket, making lookup unreliable.
A is wrong because HashMap has no mechanism to observe or react to mutations in stored keys. It computes the hash code only at insertion time and never re-checks it.
B is wrong because the mapping is not deleted — it remains in the map, just stranded in an unreachable bucket. The entry is orphaned, not removed.
D is wrong because HashMap stores a reference to the original key object, not a defensive copy. Mutations to the object are fully reflected since both the map's internal reference and your key variable point to the same instance.
As a study tip: **never use mutable objects as HashMap keys** unless you can guarantee their fields won't change after insertion. This is a classic Java pitfall that frequently appears on exams.
- What is printed when this code is executed?
Answer: 0:3:2, because pipeline traversal is deferred, and peek precedes filter so it sees every element
When you see a Java Stream question, the first thing to ask yourself is: when does work actually happen? Streams are lazy — intermediate operations like peek and filter do nothing until a terminal operation (like count) is invoked and drives the pipeline.
Here's the key insight for this question: before stream.filter(...).count() is called, the pipeline exists only as a description of work to be done. That's why the first System.out.print sees observed[0] still at 0 — peek hasn't executed yet. Once count() triggers traversal, each element flows through the pipeline in order: peek runs first (incrementing observed[0]), then filter decides whether to keep it. All three elements — 1, 2, and 3 — pass through peek, so observed[0] becomes 3. Only values 2 and 3 survive the filter value > 1, giving a count of 2. The final output is 0:3:2, confirming D is correct.
A is wrong because it assumes peek is eager and runs when the stream is constructed — streams never do work at construction time. B is wrong because it assumes peek only sees elements that pass the filter; since peek comes before filter in the pipeline, it sees all elements regardless of whether they're ultimately kept. C is wrong in the opposite direction — it claims count() skips intermediate operations entirely, but count() absolutely triggers peek and filter; it just optimizes certain pipelines in some JVM implementations, which doesn't apply here.
A handy rule: in Java streams, no work happens until a terminal operation is called, and intermediate operations execute in declaration order for each element.
- What is printed, and how does
Integer immutability affect the result? Answer: 1001:1000, because compound assignment creates and assigns another value
When you see Integer variables involved in arithmetic, the key concept to keep in mind is autoboxing, unboxing, and immutability. Integer objects in Java are immutable — once created, their internal value cannot change. But that doesn't mean a variable pointing to one can't be reassigned.
Here's what actually happens step by step: first and second both reference the same Integer object wrapping 1000. When you execute first += 1, Java unboxes first to the primitive int value 1000, adds 1 to get 1001, then boxes that result into a brand-new Integer object and assigns it back to first. The variable second is never touched — it still points to the original Integer(1000). So the output is 1001:1000, confirming B is correct.
A is wrong because it assumes += mutates the existing Integer object in place. It cannot — Integer is immutable, and no operation modifies its internal state. C is wrong in a subtle way: immutability doesn't prevent compound assignment; it just means the operation produces a new object rather than modifying the old one. The assignment to first succeeds perfectly fine. D is wrong because unboxing Integer to int is completely legal and automatic in Java — no exception is thrown.
A useful rule of thumb: whenever you see compound assignment (+=, -=, etc.) on a wrapper type, mentally translate it to "unbox → operate → rebox → reassign." The original object is untouched; only the variable's reference changes.
- What is printed, and why?
Answer: blocked:[a], because List.copyOf creates an unmodifiable detached list
When you see a Java record with a compact constructor, focus on two separate behaviors: what happens to the reference stored in the record, and what happens to the original collection after construction.
Here, the compact constructor replaces items with List.copyOf(items). This does two critical things simultaneously: it takes a snapshot of the list at construction time (so later changes to source don't affect the record), and it returns an unmodifiable list. When source.add("b") runs after new Batch(source), the batch already holds its own independent copy containing only "a". That mutation is invisible to the record. Then when batch.items().add("c") runs, List.copyOf's unmodifiable nature kicks in, throwing UnsupportedOperationException, printing "blocked:". The final println outputs [a], making C correct.
A is wrong in two ways: it says the record holds an "unmodifiable *view*" of source (incorrect — it's a detached copy, not a view), and it claims the list reflects the later "b" addition, which it never does.
B is wrong because it assumes the list remains mutable. List.copyOf explicitly returns an unmodifiable list, so .add("c") always throws rather than succeeding.
D is wrong because List.copyOf doesn't silently ignore mutations — it throws an exception. The output would include "blocked:" before [a], not just [a] alone.
Study tip: Anytime you see List.copyOf in a record constructor, remember it gives you immutability plus detachment — two guarantees, not one. Watch for distractors that conflate "view" with "copy."