Java collections

List, Set and Map — which implementation to pick, and why the interface you declare matters more than the class you use.

Choosing a collection

InterfaceCommon implementationBehaviour
ListArrayListOrdered, index access, fast append
ListLinkedListFast insert/remove near ends, slow random access
SetHashSetNo duplicates, no order guarantee
SetLinkedHashSetNo duplicates, insertion order
SetTreeSetNo duplicates, sorted
MapHashMapKey/value, fast lookup, unordered
MapTreeMapKeys kept sorted
QueueArrayDequeFast add/remove at both ends
💡
Declare the interface, not the implementation: List<String> names = new ArrayList<>(); — swapping the implementation later then touches one line.

Using them

var names = new ArrayList<String>();
names.add("Ada");
names.add("Grace");
names.add(0, "Alan");

var unique = new LinkedHashSet<>(names);
var byId = new HashMap<Integer, String>();
byId.put(1, "Ada");
byId.getOrDefault(99, "unknown");
byId.computeIfAbsent(2, k -> "created");

names.sort(Comparator.naturalOrder());
for (String n : names) System.out.println(n);
names.forEach(System.out::println);

var adults = people.stream()
    .filter(p -> p.age() >= 18)
    .map(Person::name)
    .toList();

equals and hashCode

Hash-based collections find entries by hashCode() and confirm with equals(). Override one without the other and lookups silently fail — the object lands in the wrong bucket.

public record User(int id, String email) {}   // equals/hashCode generated

// mutating a key after insertion makes an entry unreachable
var key = new MutableKey("a");
map.put(key, 1);
key.setName("b");
map.get(key);   // null - its hash changed
⚠️
Never mutate an object used as a map key or set member. Records are a good default precisely because they are immutable and generate correct equals/hashCode.

FAQ

ArrayList or array?
Arrays have fixed length and are fine for hot numeric loops. ArrayList grows, works with generics and the collections API — the normal choice.
How do I iterate and remove safely?
Use iterator.remove() or list.removeIf(predicate). Removing inside a for-each loop throws ConcurrentModificationException.

Java types and variables Exceptions and try-with-resources

Last refreshed 2026-09-17.