java.util.Collections Utility

Master java.util.Collections: sorting, searching, reversing, synchronized wrappers, singleton collections, and algorithm utilities.

published: reading time: 22 min read author: Geek Workbench
Quick Summary

Master java.util.Collections: sorting, searching, reversing, synchronized wrappers, singleton collections, and algorithm utilities. The guide uses practical examples to explain when to use, when not to use and shows how to apply the ideas in a Spring Boot project. It closes with common pitfalls and production checks so you can apply the pattern with fewer surprises.

Introduction

Before the Stream API, you had to choose between modifying a list in-place or building new collections by hand. java.util.Collections — a final utility class introduced in Java 1.2 — solved the common cases: sorting, binary search, reversal, shuffling, synchronization wrappers, and immutable collection factories.

Compare the two approaches:

List<Integer> numbers = new ArrayList<>(List.of(5, 2, 8, 1, 9));

// In-place mutation with Collections — original list is modified
Collections.sort(numbers);
System.out.println(numbers); // [1, 2, 5, 8, 9] — original list changed

// New list with Stream API — original list is untouched
List<Integer> sorted = numbers.stream().sorted().toList();
System.out.println(sorted);   // [1, 2, 5, 8, 9]
System.out.println(numbers); // [5, 2, 8, 1, 9] — still original

Both produce the same sorted result, but the trade-offs are real. Collections.sort() modifies in-place — no allocation, but the original is gone. The Stream API leaves the original untouched — safer for chaining, but allocates intermediate objects. Everything in this post flows from that distinction.

When to Use

Operation Method
Sort a list in-place Collections.sort(list)
Reverse a list Collections.reverse(list)
Binary search (pre-sorted list) Collections.binarySearch(list, key)
Shuffle (randomize) a list Collections.shuffle(list)
Make a collection thread-safe Collections.synchronizedCollection(list)
Create immutable singleton Collections.singleton(item)
Create immutable empty list/set/map Collections.emptyList(), emptySet(), emptyMap()
Fill a list with a value Collections.fill(list, value)
Rotate list elements Collections.rotate(list, distance)
Frequency of an element Collections.frequency(list, item)
Min and max Collections.min(collection), Collections.max(collection)

When NOT to Use

  • Functional transformations: If you want a new sorted list without modifying the original, use the Stream API: list.stream().sorted().toList(). The Stream approach is cleaner when you need immutability.
  • Thread-safe collections: synchronizedCollection() wraps a collection but does not make iteration safe. For concurrent read-write access, use CopyOnWriteArrayList or explicit locking around iteration.
  • Shared mutable state: Calling fill() or rotate() on a shared list overwrites data. Document when you do this intentionally, and avoid it when others expect the list to be preserved.

Collections Utility Architecture

Sorting and Searching

Collections.sort(list) sorts in-place using TimSort, a hybrid of merge sort and insertion sort. It runs in O(n log n) time and allocates O(n) auxiliary space. The sort is stable, so equal elements keep their relative order from the input. If you need a different order, sort(list, comparator) takes a comparator, so elements do not have to implement Comparable.

binarySearch() finds elements in O(log n) time, but only works on sorted lists. Call it on an unsorted list and the result is meaningless. When a key is not found, the method returns a negative value encoding the insertion point as -(insertionPoint) - 1. Callers decode this to find where to insert the missing key.

You sort once, then search repeatedly. The sort is the expensive part, so you pay that cost once and then run binary searches on the same sorted list. Re-sorting before every search wastes the effort you saved.

flowchart TD
    A1["sort(List)"]
    A2["sort(List, Comparator)"]
    A3["binarySearch(List, Key)"]
    A4["binarySearch(List, Key, Comparator)"]
    A1 --> A2
    A2 --> A3
    A3 --> A4

Mutators

These methods modify lists in-place and return nothing. They predate the Stream API and still make sense when you want to change a list without allocating new objects. reverse() flips element order, shuffle() randomizes it, fill() overwrites every element with a single value, rotate() shifts elements by a distance, and swap() exchanges two positions. All run in O(n) time with no extra allocation.

The Stream API takes the opposite approach: list.stream().sorted().toList() produces a new sorted list and leaves the original untouched. In-place sort is faster and uses less memory when you do not need the original. The Stream approach is safer when you do. Pick based on what you actually need.

flowchart TD
    B1["reverse(List)"]
    B2["shuffle(List)"]
    B3["fill(List, Element)"]
    B4["rotate(List, distance)"]
    B5["swap(List, i, j)"]

Wrappers

Wrappers are views backed by the original collection. If you modify the backing collection, the wrapper shows those changes. The three main variants are synchronizedCollection(), unmodifiableCollection(), and checkedCollection(), each adding a different layer of behavior.

synchronizedCollection() synchronizes individual mutations but not iteration. Iterate without an explicit synchronized(list) block and ConcurrentModificationException fires. For read-mostly concurrent access, CopyOnWriteArrayList is usually the better fit.

unmodifiableCollection() blocks structural changes but does not make the underlying collection immutable. Someone with a reference to the backing list can still modify it, and those changes show through the wrapper. For actual immutability, List.of() and its Java 9 siblings have no backing store to corrupt.

checkedCollection() enforces the type argument at insertion time. Adding a wrong type throws ClassCastException right then, not later at retrieval. This means you catch type errors at the point where they are easiest to fix.

flowchart TD
    C1["synchronizedCollection(List)"]
    C2["synchronizedMap(Map)"]
    C3["unmodifiableCollection(List)"]
    C4["checkedCollection(List, Type)"]

Factories

Factory methods create lightweight immutable collections for specific situations. singleton(), singletonList(), and singletonMap() wrap a single element in an immutable collection. Use these when an API expects a collection but you only have one value, or when you want a one-off set or map without the overhead of building a full collection.

emptyList(), emptySet(), and emptyMap() return shared singleton instances. Returning null forces every caller to null-check. Returning an empty collection lets callers iterate without special handling. The JVM shares these singletons internally, so there is no allocation cost.

nCopies(n, obj) creates a list holding n references to the same object. This is one object repeated n times, not n copies of the object. Mutate that object through one list entry and all n entries change. Only use nCopies() with values you guarantee will never change.

Java 9 gave us List.of(), Set.of(), and Map.of() as cleaner alternatives to the Collections.unmodifiable* factory methods. The newer methods create truly immutable collections with no backing list, making them the safer choice in security-sensitive contexts.

flowchart TD
    D1["singleton()"]
    D2["emptyList/Set/Map()"]
    D3["nCopies(n, obj)"]
    D4["listOf() / setOf() / mapOf()"]

Code Examples

import java.util.*;

// Sort in-place (natural order, must be Comparable)
List<Integer> numbers = new ArrayList<>(List.of(5, 2, 8, 1, 9));
Collections.sort(numbers);
System.out.println(numbers); // [1, 2, 5, 8, 9]

// Sort with comparator
List<String> names = new ArrayList<>(List.of("Charlie", "Alice", "Bob"));
Collections.sort(names, Comparator.comparingInt(String::length));
System.out.println(names); // [Bob, Alice, Charlie]

// Binary search — list MUST be sorted first
List<Integer> sorted = new ArrayList<>(List.of(1, 3, 5, 7, 9));
int idx = Collections.binarySearch(sorted, 5);  // 2 (index)
int neg = Collections.binarySearch(sorted, 4); // -3 (insertion point: ~(-3)-1 = -2)
int notFound = Collections.binarySearch(sorted, 10); // -6 (past end)

// Binary search with comparator
int idxComp = Collections.binarySearch(sorted, 5, Comparator.naturalOrder());

Mutators — Reverse, Shuffle, Rotate, Fill

import java.util.*;

List<Integer> list = new ArrayList<>(List.of(1, 2, 3, 4, 5));

// Reverse
Collections.reverse(list);
System.out.println(list); // [5, 4, 3, 2, 1]

// Shuffle (pseudo-random)
Collections.shuffle(list);
Collections.shuffle(list, new Random(42)); // reproducible shuffle

// Rotate — element at index i moves to (i + distance) % size
List<String> queue = new ArrayList<>(List.of("A", "B", "C", "D", "E"));
Collections.rotate(queue, 2);
System.out.println(queue); // [D, E, A, B, C] (A and B moved to end)
Collections.rotate(queue, -1); // rotate back: [E, A, B, C, D]

// Fill — replaces ALL elements with a single value
List<String> placeholders = new ArrayList<>(List.of("X", "Y", "Z"));
Collections.fill(placeholders, "TBD");
System.out.println(placeholders); // [TBD, TBD, TBD]

Thread-Safe Wrappers

import java.util.*;

List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
Set<Double> syncSet = Collections.synchronizedSet(new HashSet<>());

// Iteration requires synchronization — the wrapper does not make iteration thread-safe
synchronized (syncList) {
    for (Integer item : syncList) {
        // safe concurrent iteration
    }
}

// Unmodifiable wrapper — prevents modification
List<String> readOnly = Collections.unmodifiableList(List.of("a", "b"));
// readOnly.add("c"); // throws UnsupportedOperationException

// Checked collection — runtime type safety
List<String> checked = Collections.checkedList(new ArrayList<>(), String.class);
checked.add("hello");
// checked.add(42); // throws ClassCastException at add() time

Singleton and Empty Factories

import java.util.*;

Map<String, Integer> singletonMap = Collections.singletonMap("key", 42);
List<String> singletonList = Collections.singletonList("only");
Set<Double> singletonSet = Collections.singleton(3.14);

// Empty collections — immutable, purpose-specific
List<Object> emptyList = Collections.emptyList();
Set<Integer> emptySet = Collections.emptySet();
Map<String, Object> emptyMap = Collections.emptyMap();

// nCopies — creates an immutable list of n references to the same object
List<String> repeated = Collections.nCopies(3, "DEFAULT");
System.out.println(repeated); // [DEFAULT, DEFAULT, DEFAULT]
// Note: nCopies returns the SAME object reference repeated n times — mutation affects all entries

Algorithms — min, max, frequency, disjoint

import java.util.*;

List<Integer> nums = List.of(1, 5, 3, 7, 5, 9, 5);

// Min and max
int min = Collections.min(nums);
int max = Collections.max(nums);
String shortest = Collections.min(List.of("a", "ab", "abc"), Comparator.comparingInt(String::length));

// Frequency
int count = Collections.frequency(nums, 5); // 3

// Disjoint — true if no common elements
boolean overlap = Collections.disjoint(List.of(1, 2, 3), List.of(4, 5, 6)); // true
boolean noOverlap = Collections.disjoint(List.of(1, 2, 3), List.of(3, 4, 5)); // false

// Add all (efficient bulk add)
List<String> target = new ArrayList<>(List.of("a", "b"));
Collections.addAll(target, "c", "d", "e"); // [a, b, c, d, e]

// Replace all
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Alice"));
Collections.replaceAll(names, "Alice", "ALICE");
System.out.println(names); // [ALICE, Bob, ALICE]

Failure Scenarios

Scenario Problem Solution
binarySearch on unsorted list Incorrect index or insertion point Always sort before binary search; binarySearch does not sort
synchronizedList iteration without synchronization ConcurrentModificationException Wrap iteration in synchronized(list) block
Collections.fill destroys all existing elements Accidental overwriting of data Only use fill when you intentionally want all elements replaced
nCopies with mutable object All entries reference the same object instance Never mutate elements from an nCopies list; use Collections.nCopies only for immutable values
Collections.min on empty collection NoSuchElementException Check isEmpty() before calling min/max

Trade-off Table

Aspect Collections utility Stream API equivalent
In-place mutation Yes No (produces new collection)
Binary search Yes, in-place list.stream().sorted().toList() then binary search
Thread-safety wrapper Yes CopyOnWriteArrayList, Collections.synchronizedList()
API complexity Simple static methods Fluent, declarative
Performance Direct in-place, no allocation Allocates intermediate objects

Observability Checklist

import java.util.Collections;
import java.util.stream.Collectors;
import java.time.Instant;

// Monitored sorting
public void monitoredSort(List<Integer> data, String operationId) {
    long start = System.nanoTime();
    Collections.sort(data);
    long duration = System.nanoTime() - start;
    System.out.println("metric=sorting duration_ns=" + duration +
        " size=" + data.size() + " operation=" + operationId +
        " timestamp=" + Instant.now());
}

// Wrap to track empty collection access
public <T> List<T> safeEmptyList() {
    System.out.println("metric=empty_collection_requested type=list");
    return Collections.emptyList();
}
  • Track sorting operation frequency and duration per list size.
  • Monitor synchronizedList contention metrics in multi-threaded contexts.
  • Instrument frequency() calls to identify duplicate detection patterns.
  • Log emptyList() / emptySet() / emptyMap() calls as potential null-handling indicators.
  • Use structured metric tags for collection operations with size and operation type.

Security Notes

  • Mutation through nCopies: Collections.nCopies(n, mutableObject) shares the same object reference across all n positions. Any mutation through one reference is visible through all positions — this is a common source of accidental data corruption.
  • synchronizedList does not make iterations thread-safe: Even with the wrapper, concurrent iteration and modification throws ConcurrentModificationException. You must synchronize manually during iteration.
  • Checked collection security: Collections.checkedCollection() provides runtime type enforcement against the specific type used at creation — but it cannot prevent type-unsafe serialization or reflection-based injection of wrong types.
  • Immutable wrappers and serialization: Collections.unmodifiableList() creates a wrapper — if the underlying list is modified via a reference obtained before wrapping, the unmodifiable wrapper can reflect those changes. Always create the unmodifiable view last.

Pitfalls

  1. binarySearch requires a sorted list: If the list is not sorted, binarySearch returns an undefined result. It will NOT sort the list for you. Sort first, then search.
  2. synchronizedList / synchronizedMap are poorly named: These wrappers synchronize mutations but NOT iterations. Concurrent reads during writes require explicit synchronization — the wrapper alone is insufficient for concurrent access patterns.
  3. Collections.sort() uses TimSort: It is stable but requires O(n log n) time and O(n) auxiliary space. For nearly sorted data it approaches O(n). Sorting an already-sorted list in a parallel stream does not make it parallel — use list.parallelStream() with sorted() for parallel sort.
  4. nCopies returns the same object reference: Collections.nCopies(3, new Object()) does not create 3 distinct objects — it creates 1 object shared 3 times. This is efficient but dangerous if you later iterate and mutate.
  5. Collections.rotate(list, distance) when distance equals 0: This is a no-op — no error, but verify your distance calculation does not produce 0 unexpectedly when you expected a rotation.

Quick Recap

  • Collections.sort(list) sorts in-place (ascending by natural order); use sort(list, comparator) for custom order.
  • Collections.binarySearch(list, key) requires the list to be sorted first — returns index or negative insertion point.
  • Collections.reverse(), rotate(), shuffle(), fill() are all in-place mutators.
  • Collections.synchronizedList() / synchronizedMap() wrap but do NOT make iteration thread-safe.
  • Collections.emptyList() / emptySet() / emptyMap() return immutable, singleton empty collections.
  • Collections.singleton(), singletonList(), singletonMap() create immutable single-element collections.
  • nCopies(n, obj) creates n references to the same object — never mutate the returned list.
  • Use Stream API for read-only transformations that should remain immutable.

Interview Questions

1. What is the difference between Collections.sort() and list.stream().sorted().toList()?
Both produce a sorted list, but they make opposite trade-offs.
List<Integer> numbers = new ArrayList<>(List.of(5, 2, 8, 1, 9));

// In-place mutation — original list is modified
Collections.sort(numbers);
System.out.println(numbers); // [1, 2, 5, 8, 9] — changed permanently

// New list — original untouched
List<Integer> sorted = numbers.stream().sorted().toList();
System.out.println(sorted);   // [1, 2, 5, 8, 9]
System.out.println(numbers); // [5, 2, 8, 1, 9] — still original

Collections.sort() uses TimSort in O(n log n) time with O(n) auxiliary space and is stable. The Stream API version allocates intermediate objects but leaves the original untouched. Use sort() when you want mutation; use Stream API when you need immutability or chaining.

2. What does Collections.synchronizedList() actually synchronize?
It synchronizes individual mutations but NOT iteration — this is the most common trap with these wrappers.
List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
syncList.add(1);
syncList.add(2);
syncList.add(3);

// WRONG — throws ConcurrentModificationException
for (Integer item : syncList) {
    // safe concurrent iteration
}

// CORRECT — wrap the iteration block
synchronized (syncList) {
    for (Integer item : syncList) {
        // safe
    }
}

Each add(), remove(), set(), and clear() is synchronized individually, but the wrapper cannot know when you will iterate. You must hold the monitor yourself during iteration. For read-mostly concurrent access, CopyOnWriteArrayList is usually the better fit — it optimises for frequent reads over frequent writes.

3. What is the result of binarySearch when the key is not found?
It returns a negative number encoding the insertion point, so you can recover both the fact of absence and where to insert.
List<Integer> sorted = new ArrayList<>(List.of(1, 3, 5, 7, 9));

int idx = Collections.binarySearch(sorted, 5);  // 2 (found at index 2)
int neg = Collections.binarySearch(sorted, 4);  // -3 (not found)
// Recovery: insertion point = -(result) - 1 = -(-3) - 1 = 2

int notFound = Collections.binarySearch(sorted, 10); // -6 (past end)
// insertion point = -(result) - 1 = -(-6) - 1 = 5 (append at end)

The formula is -(insertionPoint) - 1. So a return value of -3 means insertion point 2. A return of -6 means insertion point 5 — past the last element. Always sort before searching; binarySearch does not sort for you.

4. Why is Collections.nCopies dangerous with mutable objects?
It creates one object and returns n references to it — this is the classic aliasing bug.
// WRONG — nCopies with a mutable object
List<StringBuilder> bad = new ArrayList<>(Collections.nCopies(3, new StringBuilder("X")));
bad.get(0).append(" updated");
System.out.println(bad); // [X updated, X updated, X updated] — all three changed!

// CORRECT — nCopies with immutable objects only
List<String> safe = Collections.nCopies(3, "DEFAULT");
System.out.println(safe); // [DEFAULT, DEFAULT, DEFAULT]
// safe.get(0).append("!"); // String has no append — won't compile

One object. n references. Mutate through one index and every entry reflects it. Use nCopies only with objects you can guarantee will never change — primitives, strings, or immutable value types.

5. What is the difference between Collections.unmodifiableList() and List.of()?
The difference is whether a backing store exists that can corrupt the wrapper.
// unmodifiableList — wrapper, not a copy
List<String> original = new ArrayList<>(List.of("a", "b"));
List<String> view = Collections.unmodifiableList(original);
original.add("c");                         // modifies backing list
System.out.println(view); // [a, b, c]    — view reflects the change!

// List.of() — true immutability, no backing store
List<String> trulyImmutable = List.of("a", "b");
trulyImmutable.add("c");                  // throws UnsupportedOperationException
// No backing list exists to corrupt it

Collections.unmodifiableList() wraps the original — it does not copy it. Anyone with a reference to the underlying list can still modify it. List.of() (Java 9+) creates a standalone immutable list with no backing store. For security-sensitive contexts, prefer List.of().

6. What is the purpose of Collections.disjoint() and when should you use it?
Collections.disjoint(c1, c2) returns true if the two collections have no elements in common. This is useful for checking preconditions before merging collections, validating that two datasets do not overlap, or checking whether a set of permissions conflicts with a set of restrictions."
7. What does Collections.rotate() do and how does the distance parameter work?
It shifts elements by wrapping around the ends — element at index i moves to (i + distance) % size.
List<String> queue = new ArrayList<>(List.of("A", "B", "C", "D", "E"));

Collections.rotate(queue, 2);
System.out.println(queue); // [D, E, A, B, C]

// Positive = shift forward (head moves to tail)
// Negative = shift backward (tail moves to head)
Collections.rotate(queue, -1);
System.out.println(queue); // [E, A, B, C, D]

// Common use: rotating a circular buffer or deck of cards
// distance = 0 is a no-op — no exception, nothing happens
Collections.rotate(queue, 0); // queue unchanged

A distance of 0 silently does nothing — no exception thrown. Verify your distance calculation does not produce 0 when you expected a rotation.

8. How does Collections.replaceAll differ from iterating with set()?
Collections.replaceAll(list, oldVal, newVal) replaces all occurrences of oldVal with newVal in place, returning true if any replacement was made. Iterating manually with set() and checking equality is equivalent but more verbose. Both require the list to support set() operation."
9. What is the behavior of Collections.shuffle() with a Random seed?
Collections.shuffle(list, new Random(seed)) produces a deterministic, reproducible shuffle — the same seed always produces the same ordering after shuffling. Without a seed, it uses new Random() which is seeded nondeterministically. This is useful for testing scenarios where you need reproducible ordering."
10. What does Collections.frequency() return for an empty collection or absent element?
Collections.frequency(collection, element) returns 0 when the collection is empty or when the element is not present in the collection. This is always the correct count — there is no ambiguity. The method uses equals() for comparison."
11. What is the difference between Collections.emptyIterator() and returning a new empty iterator?
Collections.emptyIterator() returns a shared singleton instance — the same object is returned on every call. This avoids allocation for the common no elements case. Creating a new ArrayListIterator() or similar allocates a new object each time. The singleton is immutable and thread-safe."
12. When should you use Collections.nCopies() versus creating an actual list of identical elements?
Use nCopies(n, obj) when you want an immutable list of n references to the same object — this is efficient and appropriate for read-only scenarios. However, if any element in the returned list is mutated, all entries reflect the change because they share the same reference. If you need independent copies, create a new list with explicit copies."
13. What does Collections.addAll() do and why is it more efficient than multiple add() calls?
Collections.addAll(collection, elements...) adds all provided elements to the collection in a single call. It is more efficient than calling add() in a loop because the implementation can resize the underlying array fewer times when adding many elements. It is also more concise."
14. What is the difference between Collections.max() and Collections.min() with and without a comparator?
Collections.max(collection) uses natural ordering and returns the largest element. Collections.max(collection, comparator) uses the provided comparator. Collections.min() works the same way, returning the smallest. Both throw NoSuchElementException if the collection is empty."
15. How does Collections.binarySearch() behave when the list is not sorted?
The result is undefined — it may return an index that happens to contain the key, or a negative value, with no indication that the list was unsorted.
List<Integer> unsorted = new ArrayList<>(List.of(9, 3, 1, 5, 2));

// WRONG — unsorted list, result is garbage
int wrong = Collections.binarySearch(unsorted, 3); // unpredictable!

// CORRECT — sort first, then search
Collections.sort(unsorted);  // [1, 2, 3, 5, 9]
int correct = Collections.binarySearch(unsorted, 3); // 2 ✓

binarySearch() does not sort, validate, or warn. It trusts the caller completely. The only symptom of an unsorted list is a wrong result with no error thrown. The standard pattern is one sort() followed by many binarySearch() calls on the same sorted list — pay the sort cost once, search cheaply many times.

16. What is the result of calling Collections.min() on a collection containing null elements?
If the collection's comparator does not handle nulls, Collections.min() throws NullPointerException. If using a comparator that sorts nulls first or last, the result depends on the comparator. By default, Comparator.naturalOrder() throws NPE on nulls."
17. What is the purpose of Collections.fill() and when should you use it?
Collections.fill(list, element) replaces all elements of the provided list with a single value. This mutates the list in place, overwriting previous contents entirely. It is useful for resetting a list to a default or placeholder state. It does not change the list size."
18. What is the difference between Collections.checkedCollection() and a cast in the caller?
Collections.checkedCollection(collection, type) wraps a collection with a runtime type check on every mutation operation. If a wrong type is added, it throws ClassCastException immediately rather than at read time. A simple cast in the caller only fails at the point of cast, not at insertion."
19. How does Collections.indexOfSubList() work and what does the return value mean?
Collections.indexOfSubList(super, sub) searches the super list for the first occurrence of the sub list and returns the starting index, or -1 if not found. It returns the lowest index i such that super.subList(i, i + sub.size()).equals(sub)."
20. What happens when Collections.reverse() is called on an empty list or single-element list?
Collections.reverse(emptyList) and Collections.reverse(singletonList) are both no-ops — the list is already in its reversed state with only one possible ordering. No exception is thrown and the method returns immediately."

Further Reading

Conclusion

java.util.Collections predates the Stream API, but it is far from obsolete. I still reach for it whenever I need actual in-place mutation — the Stream API produces new collections, not modified ones.

The sort() and binarySearch() pair trips up developers most. Remember: binarySearch() does not sort for you. Sort first, then search. Collections.sort() uses TimSort and is stable — equal elements keep their relative order — but needs O(n) auxiliary space.

The synchronization wrappers have a trap: they synchronize mutations but not iteration. Concurrent modification during iteration throws ConcurrentModificationException regardless of the wrapper. If you need concurrent iteration, wrap it in a synchronized(list) block, or reach for CopyOnWriteArrayList or the concurrent collections instead.

Factory methods like singleton(), emptyList(), and nCopies() are useful for guard clauses and default values. Watch out for nCopies() — all n entries point to the same object. Mutate one, and every entry changes.

Streams and Collections fit together naturally. Streams eat collections, and Comparator expressions used with sort() tap into the same java.util.function interfaces Streams do.


Category

Related Posts

Abstract Classes in Java

Learn about partially implemented classes that define contracts for subclasses using abstract methods and concrete implementations.

#java-abstract-classes #java #java-fundamentals

Arithmetic Operators in Java

Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.

#java-arithmetic-operators #java #java-fundamentals

Array Basics in Java

Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.

#java-array-basics #java #java-fundamentals