Unmodifiable vs Immutable Collections
Java has two kinds of "read-only" collections, and they are often confused. An unmodifiable view, created with Collections.unmodifiableList(list), blocks changes made through the view, but it still reflects changes made to the original list behind it. An immutable collection, created with List.of or List.copyOf, has no original behind it, so its contents never change. In this lesson, you will see the difference in code and learn when to use each.
Example: A View vs a Copy
Let's see an example where the original list changes after both are created.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ViewVsCopy {
public static void main(String[] args) {
List<String> original = new ArrayList<>(List.of("Navika", "Srihitha"));
List<String> view = Collections.unmodifiableList(original);
List<String> copy = List.copyOf(original);
original.add("Advitha");
System.out.println("original: " + original);
System.out.println("view: " + view);
System.out.println("copy: " + copy);
try {
view.add("Anamaneni");
} catch (UnsupportedOperationException e) {
System.out.println("view.add rejected");
}
}
}original: [Navika, Srihitha, Advitha]
view: [Navika, Srihitha, Advitha]
copy: [Navika, Srihitha]
view.add rejectedExplanation: Both the view and the copy reject add. But when the original list gained Advitha, the view showed it too, because the view is just a read-only window onto the original. The copy didn't change, because it holds its own elements.
Differences
| Unmodifiable view | Immutable collection | |
|---|---|---|
| Created with | Collections.unmodifiableList/Set/Map(...) | List.of, Set.of, Map.of, copyOf, stream.toList() |
| Changes through it | Rejected | Rejected |
| Changes to the original | Visible | Not applicable: there's no original |
| Copies the elements | No, it wraps | Yes |
null elements | Allowed if the original has them | Not allowed |
| Cost to create | Very cheap (a small wrapper) | Copies the elements |
When to Use Each
- Use an unmodifiable view when a class wants to let callers see live data that the class itself keeps updating, without letting them change it. For example, a playlist object can return a read-only view of its songs.
- Use an immutable copy when you want a snapshot that will never change, for example storing a list passed into a constructor, or sharing data between threads.
Common Mistakes
Assuming Collections.unmodifiableList makes the data safe from all changes. Anyone holding the original list can still change it.
Wrapping a list twice to "make it more immutable". It's still a view of the same original.
Calling List.copyOf on an already-immutable list and expecting a new copy. It may return the same object, which is fine because it can't change.
Interview Corner
What is the difference between an unmodifiable and an immutable collection?
An unmodifiable collection, such as the one returned by Collections.unmodifiableList, is a read-only wrapper around another collection: modification methods throw UnsupportedOperationException, but changes made to the underlying collection are visible through the wrapper. An immutable collection, such as one from List.of or List.copyOf, holds its own elements and can never change by any route. Unmodifiable views are cheap and live; immutable collections are snapshots that are safe to share.
Practice
- Easy: Repeat the example with a
Map: create an unmodifiable view and aMap.copyOf, thenputa new key in the original and print all three. - Harder: Write a
Courseclass that keeps an internalArrayListof students, offersaddStudent, and exposesstudents()as an unmodifiable view. Show that a caller sees new students but can't add any.
Key Takeaways
Was this tutorial helpful?