Skip to content
JavaAgentic

Type at least two characters. Try “RAG”, “pgvector” or “tool calling”.

The Collections Framework Map

The interface hierarchy and what each contract promises, why some methods throw UnsupportedOperationException by design, and the differences between Arrays.asList, List.of and List.copyOf.

Beginner6 min readUpdated
On this page

Knowing which collection to reach for is a daily decision, and interviewers use it as a proxy for whether you understand the trade-offs underneath. This page is the map of the framework and the contracts each interface promises; the pages that follow open up the implementations.

Key Takeaways

  • Collection is the root for List, Set and Queue. Map is deliberately outside it.
  • Interfaces define contracts; some operations are optional, which is why UnsupportedOperationException exists in a compiled language.
  • Arrays.asList is a fixed-size view, List.of is immutable and null-hostile, List.copyOf is an immutable snapshot. All three behave differently.
  • Map's three views — keySet, values, entrySet — are live windows, not copies.
  • Choose by the operation you perform most: lookup by key, ordered iteration, index access, or first/last removal.

The hierarchy

Map sits outside Collection because an association is not an element. LinkedList implements both List and Deque.

Each interface adds one promise to the one above it:

Iterable promises only that you can walk it once, in some order, with a for-each loop.

Collection adds size, membership testing, and bulk operations — add, remove, contains, stream. It says nothing about order or duplicates.

List adds positional access and permits duplicates. Its contract is an ordered sequence with an index, which is what makes get(i), add(i, e) and indexOf meaningful.

Set forbids duplicates, defined by equals. It adds no methods at all — the entire difference from Collection is a documented behavioural constraint, which is a nice illustration that a Java interface contract is more than its method list.

Queue adds an intended ordering for insertion and removal, usually FIFO. Deque allows both ends. SortedSet and NavigableSet add range queries: first, last, headSet, ceiling, floor.

Map is a separate hierarchy of key-to-value associations. SortedMap and NavigableMap add the same range operations as their set counterparts.

LinkedList is the odd one out: it implements both List and Deque, which is why it appears in answers to questions about both. In practice ArrayDeque is the better deque and ArrayList the better list, so LinkedList is rarely the right choice for either job.

Optional operations

Java's collection interfaces are older than default methods, and they solve the "not every implementation supports every method" problem with documentation plus a runtime exception rather than a finer-grained type hierarchy.

the same interface, three behaviours
List<String> mutable = new ArrayList<>(List.of("a", "b"));
List<String> fixed   = Arrays.asList("a", "b");
List<String> immutable = List.of("a", "b");
 
mutable.set(0, "z");     // ok
fixed.set(0, "z");       // ok — writes through to the backing array
immutable.set(0, "z");   // UnsupportedOperationException
 
mutable.add("c");        // ok
fixed.add("c");          // UnsupportedOperationException — cannot resize an array
immutable.add("c");      // UnsupportedOperationException

This design is widely criticised, and knowing why it exists is a better interview answer than agreeing it is bad: splitting the hierarchy into mutable and immutable interfaces would have doubled the number of types and forced every method that accepts a List to choose. The JDK traded compile- time safety for a simpler API surface, and documented the cost.

The practical consequence is that the type of a parameter tells you nothing about whether you may mutate it. A method that takes a List and calls add on it works with the caller's ArrayList and throws with the caller's List.of. Either copy defensively or document the requirement.

The three factory methods

FactoryMutable?Nulls?Backed by
new ArrayList<>(...)FullyYesIts own array
Arrays.asList(a, b)set onlyYesThe caller's array
List.of(a, b)NoThrowsIts own compact storage
List.copyOf(other)NoThrowsA snapshot
Collections.unmodifiableList(l)NoYesA live view of l

Three of these have caught people out often enough to be interview staples.

Arrays.asList on a primitive array does not do what it looks like: Arrays.asList(new int[]{1,2,3}) produces a single-element List<int[]>, because int[] is one object rather than three Integers. Use Arrays.stream(arr).boxed().toList().

List.of rejects null, both on construction and in contains(null). Code migrating from Arrays.asList to List.of for immutability sometimes starts throwing NullPointerException from a contains check that used to return false.

Collections.unmodifiableList returns a view. Mutating the underlying list changes what the view shows. It prevents the recipient from mutating; it does not give you a snapshot. That distinction is covered in Immutable objects and defensive copying.

Map views are live

the views are windows, not copies
Map<String, Integer> scores = new HashMap<>(Map.of("a", 1, "b", 2, "c", 3));
 
Set<String> keys = scores.keySet();
keys.remove("a");                       // removes the ENTRY from scores
scores.size();                          // 2
 
scores.values().removeIf(v -> v > 2);   // removes entries whose value is > 2
scores.size();                          // 1
 
for (Map.Entry<String, Integer> e : scores.entrySet()) {
    e.setValue(e.getValue() * 10);      // writes through to the map
}

keySet(), values() and entrySet() are backed by the map, so removal propagates and Entry.setValue updates it. You cannot add through them — there would be no value to associate — and iterating one while structurally modifying the map throws ConcurrentModificationException, as covered in Fail-fast versus fail-safe iterators.

Iterating entrySet() is also the correct way to walk a map. Iterating keySet() and calling get(key) inside the loop performs a second hash lookup per entry, which is a small but pointless cost and a detail interviewers notice.

Choosing one

The decision is almost always driven by the operation you perform most often:

  • Lookup by keyHashMap. Add LinkedHashMap if you need insertion or access order, TreeMap if you need sorted keys or range queries.
  • Index access, iteration, appendArrayList. Effectively always, for the reasons in ArrayList vs LinkedList.
  • Membership testingHashSet. LinkedHashSet when iteration order must be stable, TreeSet when you need sorted order or ceiling/floor.
  • Add and remove at both endsArrayDeque. It is faster than LinkedList and faster than Stack, which is a synchronised Java 1.0 class nobody should still be using.
  • Producer-consumer between threads — a BlockingQueue, always bounded.
  • Shared across threadsConcurrentHashMap, CopyOnWriteArrayList for read-mostly, or a concurrent queue. Never a HashMap guarded by hope.

The follow-up worth preparing for is "and what if the collection is read far more often than it is written?" — the answer being CopyOnWriteArrayList or an immutable collection replaced wholesale, both of which trade write cost for lock-free reads.

What gets asked

Expect: draw the hierarchy; why Map is not a Collection; the difference between Collection and Collections; and the Arrays.asList behaviour. The last one is the most common, and the complete answer names all three states — mutable, fixed-size, immutable — rather than just saying "it is immutable", which is wrong.

Frequently Asked Questions

Why is Map not a Collection?
Because a Collection is a group of individual elements and a Map is a group of key-value associations, so almost none of the Collection methods make sense on it. What would add(x) mean on a Map? Instead, Map exposes three collection views — keySet(), values() and entrySet() — which are Collections backed by the map, so removing from keySet() removes the entry from the map itself.
Why does Arrays.asList(...).add(...) throw UnsupportedOperationException?
Arrays.asList returns a fixed-size list that is a view over the original array, not a copy. set() works and writes through to the array, but add() and remove() would need to change the array length, which is impossible. It is one of three different immutability behaviours in the JDK and the most surprising, because it is mutable in one direction only.
What is the difference between Collection and Collections?
Collection is the root interface of the framework, implemented by List, Set and Queue. Collections is a utility class of static methods — sort, unmodifiableList, synchronizedMap, emptyList, reverse. The naming is unfortunate and the question is a quick check that you have actually read the API rather than only used it through an IDE.

Related tutorials