ExerciseChallenge
Challenge
Close both directions
20 minintermediate2–10 yrs
Edge cases. You have to reason, and two valid fixes differ.
What this teaches
- Collections.unmodifiableList is a view of a list somebody else can change
- List.copyOf severs the connection, and is free when the input is already immutable
- Arrays.asList is a fixed-size view of the array — set works, add throws
- Each component type needs a different technique, and some have none
Starter
Starter.javaOpen in playground
import java.util.*;
/**
* CHALLENGE — 20 minutes.
*
* Part 1 is a measurement: four ways of "protecting" a list, and they do
* four different things. Part 2 is the fix that follows from it.
*
* TASKS
* 1. Run part 1 and note which of the four reflects a later change to the
* source, and which of them refuses writes.
* 2. Note what List.copyOf returned for an input that was already
* immutable. That fact answers the "copying is too slow" objection.
* 3. Make every assertion in part 2 print true, by fixing Shipment only.
* Each of the three components needs a different technique.
* 4. In a comment: why does Arrays.asList allow set but not add?
*/
public class Starter {
static final class Shipment {
private final String reference;
private final List<String> parcels;
private final Date dispatchedAt;
Shipment(String reference, List<String> parcels, Date dispatchedAt) {
this.reference = reference;
this.parcels = parcels;
this.dispatchedAt = dispatchedAt;
}
String reference() { return reference; }
List<String> parcels() { return parcels; }
Date dispatchedAt() { return dispatchedAt; }
}
public static void main(String[] args) {
System.out.println("── part 1: four kinds of 'protected' ──");
List<String> source = new ArrayList<>(List.of("first"));
List<String> view = Collections.unmodifiableList(source);
List<String> copy = List.copyOf(source);
List<String> mutableCopy = new ArrayList<>(source);
List<String> arrayView = Arrays.asList("a", "b");
source.add("added later");
System.out.println(" unmodifiableList sees it : " + view);
System.out.println(" List.copyOf sees it : " + copy);
System.out.println(" new ArrayList sees it : " + mutableCopy);
System.out.println(" copyOf of an immutable list is the same instance : "
+ (List.copyOf(copy) == copy));
arrayView.set(0, "z");
System.out.println(" Arrays.asList after set : " + arrayView);
try {
arrayView.add("c");
} catch (UnsupportedOperationException e) {
System.out.println(" Arrays.asList add : UnsupportedOperationException");
}
System.out.println();
System.out.println("── part 2: fix Shipment ──");
List<String> parcels = new ArrayList<>(List.of("P-1"));
Date dispatched = new Date(1_700_000_000_000L);
Shipment shipment = new Shipment("SH-1", parcels, dispatched);
int parcelsAtStart = shipment.parcels().size();
long timeAtStart = shipment.dispatchedAt().getTime();
// Four attacks. All four should leave the shipment untouched.
parcels.add("added by the caller");
dispatched.setTime(0);
try { shipment.parcels().add("added by a reader"); } catch (UnsupportedOperationException ignored) { }
shipment.dispatchedAt().setTime(0);
boolean parcelsIntact = shipment.parcels().size() == parcelsAtStart;
boolean timeIntact = shipment.dispatchedAt().getTime() == timeAtStart;
System.out.println(" parcels unchanged : " + parcelsIntact);
System.out.println(" dispatch unchanged: " + timeIntact);
System.out.println();
System.out.println(parcelsIntact && timeIntact
? "Shipment is immutable" : "Shipment is NOT immutable yet");
}
}Run it locally:
cd exercises/java/oop/immutability-in-practice/02-challenge
javac Starter.java -d /tmp/out && java -cp /tmp/out StarterHints
Hint 1
Run the view-vs-copy comparison first. One of them changes under you.
Hint 2
Check whether List.copyOf allocates when handed a List.of — the answer decides whether the 'too slow' objection is real.
Hint 3
For the Date field you need both halves. Ask what the getter is handing out and whether there is a second copy anywhere.
Hint 4
Arrays.asList: try set and then add on the same list, and explain the difference from what it is a view OF.
Done when
- Every assertion in main() prints true
- No component can be reached or changed from outside, in either direction
- A comment records what List.copyOf returned for an already-immutable input
- A comment explains why Arrays.asList allows set but not add
Stretch
Add a component whose type you do not control and which is mutable with no
copy constructor — assume a third-party class with public fields. Work out
what your options are: wrap it in your own immutable view, copy it
field-by-field, or accept the leak and document it. Pick one and say what
you would write in the class javadoc so the next reader knows.