Encodera

Thanks for reading!

Donate to the site — we work hard for you.

How to create an immutable collection

Short answer

The factory methods List.of(), Set.of() and Map.of() create collections that cannot be changed:

void main() {
    List<String> roles = List.of("admin", "editor");
    Map<String, Integer> limits = Map.of("free", 10, "pro", 100);
 
    IO.println(roles);
    IO.println(limits.get("pro"));
}

Output:

[admin, editor]
100

A copy of an existing collection

List.copyOf() makes an unmodifiable copy. Later changes to the original do not affect it:

void main() {
    var names = new ArrayList<>(List.of("Anna"));
    var snapshot = List.copyOf(names);
    names.add("Oleg");
 
    IO.println(names);
    IO.println(snapshot);
}

Output:

[Anna, Oleg]
[Anna]

A read-only view

Collections.unmodifiableList() does not copy the data, it only forbids changes through itself. Changes to the original are visible in it:

void main() {
    var names = new ArrayList<>(List.of("Anna"));
    var view = Collections.unmodifiableList(names);
    names.add("Oleg");
 
    IO.println(view);
}

Output:

[Anna, Oleg]

From a stream

A stream's toList() also returns an unmodifiable list:

void main() {
    var evens = Stream.of(1, 2, 3, 4).filter(n -> n % 2 == 0).toList();
    IO.println(evens);
    evens.add(6);
}

Output:

[2, 4]
Exception in thread "main" java.lang.UnsupportedOperationException

Common mistake

Assuming the elements are immutable too. The collection does not let you add or remove elements, but a mutable object inside can still be changed:

void main() {
    var lists = List.of(new ArrayList<>(List.of(1)));
    lists.getFirst().add(2);
 
    IO.println(lists);
}

Output:

[[1, 2]]

For full immutability the elements must be immutable too — for example, records with strings and numbers.

Functions used in this recipe

Thanks for reading!

Donate to the site — we work hard for you.