Java Collections in Practice
The Java Collections Framework provides lists, sets, maps, and queues with well-defined iteration and concurrency behavior. Choosing the right type affects correctness, performance, and scalability in real applications. This article focuses on mental models, when to use which abstraction, and when to switch to concurrent collections.
Overview
- List: Ordered, allows duplicates.
ArrayListis the default for random access;LinkedListis rarely better and has worse cache behavior in most cases. - Set: No duplicates.
HashSetfor unordered,LinkedHashSetfor insertion order,TreeSetfor sorted order (and higher cost). - Map: Key-value.
HashMap,LinkedHashMap,TreeMapmirror the set choices. UseTreeMaponly when you need order or range operations. - Queue: For producer-consumer or "process in order" patterns.
ArrayDequeis a good default for a single-threaded queue or stack.
In multi-threaded code, prefer ConcurrentHashMap over synchronizing a plain HashMap for shared state. Use concurrent queues such as ConcurrentLinkedQueue, LinkedBlockingQueue, or similar when multiple threads produce or consume.
Example
A shared cache used by many request-handling threads is a good fit for ConcurrentHashMap:
Java// Shared across request-handling threads private final ConcurrentHashMap<String, UserSession> cache = new ConcurrentHashMap<>(); public UserSession getOrLoad(String userId) { return cache.computeIfAbsent(userId, id -> loadSessionFromDb(id)); }
computeIfAbsentatomically checks and computes; only one thread runs the loader per key. No external locking is needed.- If you used a plain
HashMapwithsynchronizedon the whole map, contention on a single lock would limit throughput. WithConcurrentHashMap, different buckets can be updated in parallel, giving better scalability for read-heavy or mixed workloads.
For a single-threaded queue or stack, ArrayDeque is a good default:
JavaDeque<Task> stack = new ArrayDeque<>(); stack.push(task); Task next = stack.pop();
Dequeis the interface;push()andpop()are stack-style operations. For a FIFO queue you would useoffer()andpoll().
Core Mechanism / Behavior
- ArrayList: Resizable array;
addis amortized O(1),get/setby index is O(1). Good cache locality. - HashMap: Hashing and buckets; average
get/putis O(1) with a good hash function. Iteration order is undefined unless you useLinkedHashMap. - TreeMap: Red-black tree;
get/putis O(log n); keys are ordered. Use when you need order or range operations. - Concurrent collections: Lock striping or lock-free structures so different threads can make progress without blocking each other on every operation.
ConcurrentHashMapis usually a better fit thanCollections.synchronizedMap(new HashMap())for concurrent access. - Queues: Use
ArrayDequefor single-threaded queue/stack; useConcurrentLinkedQueue,LinkedBlockingQueue, or similar when multiple threads produce or consume.
Key Rules
- Assuming iteration is thread-safe: Iterating over a non-concurrent collection while another thread mutates it can throw
ConcurrentModificationExceptionor show undefined behavior. Use concurrent collections or explicit locking. - Using HashMap when order matters: If you need insertion or access order, use
LinkedHashMap. If you need sort order, useTreeMap. - Overusing LinkedList: For most list use cases,
ArrayListis faster and simpler. ReserveLinkedListfor frequent insert/remove in the middle when you already have the node reference. - Choose by need: order, uniqueness, concurrency, or specific access patterns. Prefer
ArrayListandHashMap(orLinkedHashMap) by default; switch to concurrent variants when multiple threads share the structure; use tree-based structures only when you need ordering or range operations.
What's Next
For deep dives on specific types, see HashMap deep dive and ArrayList vs LinkedList. For concurrency, see ConcurrentHashMap internals.