Contents
- sort, reverse, binarySearch
- shuffle, fill, copy, nCopies
- min, max, frequency, disjoint
- Unmodifiable Wrappers
- Synchronized Wrappers
Collections.sort() performs a stable O(n log n) sort in-place. reverse() reverses the current order without sorting. binarySearch() requires a pre-sorted list and returns the index of the target, or a negative value indicating where it would be inserted:
import java.util.*;
List<Integer> nums = new ArrayList<>(List.of(3, 1, 4, 1, 5, 9, 2, 6));
// sort — stable, O(n log n)
Collections.sort(nums);
System.out.println(nums); // [1, 1, 2, 3, 4, 5, 6, 9]
// sort with Comparator
Collections.sort(nums, Comparator.reverseOrder());
System.out.println(nums); // [9, 6, 5, 4, 3, 2, 1, 1]
// reverse — reverses current order (not the same as sort descending)
Collections.reverse(nums);
System.out.println(nums); // [1, 1, 2, 3, 4, 5, 6, 9] — reversed back
// binarySearch — list MUST be sorted ascending; returns index or (-(insertion point) - 1)
Collections.sort(nums);
int idx = Collections.binarySearch(nums, 5);
System.out.println(idx); // e.g. 5 (index of value 5)
int missing = Collections.binarySearch(nums, 7);
System.out.println(missing); // negative (not found)
// binarySearch with Comparator — list must be sorted with same Comparator
List<String> words = new ArrayList<>(List.of("Apple", "banana", "Cherry"));
Collections.sort(words, String.CASE_INSENSITIVE_ORDER);
int pos = Collections.binarySearch(words, "cherry", String.CASE_INSENSITIVE_ORDER);
System.out.println(pos); // found index
// rotate — rotates elements by distance positions
List<Integer> r = new ArrayList<>(List.of(1, 2, 3, 4, 5));
Collections.rotate(r, 2); // right-rotate by 2
System.out.println(r); // [4, 5, 1, 2, 3]
shuffle() randomises a list in-place using a Random source. fill() replaces every element with a single value. nCopies() creates an immutable list of repeated elements — handy for initialising a mutable list via the copy constructor. copy() bulk-copies elements into an existing list:
List<Integer> deck = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
// shuffle — randomizes in-place using default Random (or supplied Random for reproducibility)
Collections.shuffle(deck);
System.out.println(deck); // random order
Collections.shuffle(deck, new Random(42)); // seeded — reproducible shuffle
// fill — replace all elements with a single value
List<String> grid = new ArrayList<>(Collections.nCopies(9, "")); // 9 empty strings
Collections.fill(grid, "X");
System.out.println(grid); // [X, X, X, X, X, X, X, X, X]
// nCopies — immutable list with n copies of an object
List<Integer> zeros = Collections.nCopies(5, 0);
System.out.println(zeros); // [0, 0, 0, 0, 0]
// Useful for pre-populating or initializing arrays
// copy — copies src elements into dest (dest must be at least as large as src)
List<Integer> src = List.of(10, 20, 30);
List<Integer> dest = new ArrayList<>(Collections.nCopies(5, 0));
Collections.copy(dest, src); // dest[0..2] = src[0..2]
System.out.println(dest); // [10, 20, 30, 0, 0]
// swap — swap two elements by index
List<String> letters = new ArrayList<>(List.of("a", "b", "c", "d"));
Collections.swap(letters, 0, 3);
System.out.println(letters); // [d, b, c, a]
// replaceAll — replace all occurrences of oldVal with newVal
List<String> data = new ArrayList<>(List.of("a", "b", "a", "c", "a"));
Collections.replaceAll(data, "a", "X"); // not the same as List.replaceAll(UnaryOperator)
System.out.println(data); // [X, b, X, c, X]
Collections.min() and max() scan a collection for its extreme values using natural order or a Comparator. frequency() counts occurrences of an element. disjoint() returns true if two collections share no elements:
List<Integer> values = List.of(3, 1, 4, 1, 5, 9, 2, 6, 5, 5);
// min and max — uses natural order (or Comparator)
System.out.println(Collections.min(values)); // 1
System.out.println(Collections.max(values)); // 9
// With Comparator
List<String> words = List.of("fig", "apple", "banana");
System.out.println(Collections.min(words, Comparator.comparingInt(String::length))); // "fig"
System.out.println(Collections.max(words, Comparator.comparingInt(String::length))); // "banana"
// frequency — count occurrences of an element
System.out.println(Collections.frequency(values, 5)); // 3
System.out.println(Collections.frequency(values, 7)); // 0
// disjoint — true if no element in common
List<Integer> a = List.of(1, 2, 3);
List<Integer> b = List.of(4, 5, 6);
List<Integer> c = List.of(3, 4, 5);
System.out.println(Collections.disjoint(a, b)); // true — no common elements
System.out.println(Collections.disjoint(a, c)); // false — 3 is in both
// Works with any two Collection types
Set<String> set = Set.of("x", "y");
List<String> list = List.of("a", "b", "x");
System.out.println(Collections.disjoint(set, list)); // false — "x" in common
// Practical: count word frequencies in a list
List<String> tokens = List.of("the", "cat", "sat", "on", "the", "mat", "the");
Set<String> unique = new HashSet<>(tokens);
unique.forEach(w -> System.out.println(w + ": " + Collections.frequency(tokens, w)));
// For large datasets, use a HashMap counter instead — O(n) vs O(n * unique)
Collections.unmodifiableList() and its siblings create read-only views — mutation methods throw UnsupportedOperationException. Crucially, the view is backed by the original collection, so changes to the original are still visible. For a true immutable snapshot, use List.copyOf() (Java 10+):
// Collections.unmodifiable* — read-only view of a collection
// Mutations throw UnsupportedOperationException
List<String> mutable = new ArrayList<>(List.of("a", "b", "c"));
List<String> readOnly = Collections.unmodifiableList(mutable);
System.out.println(readOnly.get(0)); // "a" — reads work
// readOnly.add("d"); // UnsupportedOperationException
// IMPORTANT: the view is backed by the original — changes to mutable show in readOnly
mutable.add("d");
System.out.println(readOnly.size()); // 4 — sees the change!
// For a true immutable snapshot, use List.copyOf() (Java 10+) or List.of()
List<String> snapshot = List.copyOf(mutable); // independent copy, immutable
// Other unmodifiable wrappers
Set<String> uSet = Collections.unmodifiableSet(new HashSet<>());
Map<String, Integer> uMap = Collections.unmodifiableMap(new HashMap<>());
SortedSet<String> uSSet = Collections.unmodifiableSortedSet(new TreeSet<>());
SortedMap<String,?> uSMap = Collections.unmodifiableSortedMap(new TreeMap<>());
// Singleton collections — single-element immutable collections
List<String> one = Collections.singletonList("only");
Set<String> ons = Collections.singleton("only");
Map<String, Integer> onm = Collections.singletonMap("key", 42);
// emptyList/emptySet/emptyMap — canonical immutable empty collections
List<String> empty = Collections.emptyList(); // type-safe, no allocation
// Prefer List.of(), Set.of(), Map.of() for modern code (Java 9+)
Prefer List.of(), Set.of(), and Map.of() (Java 9+) over Collections.unmodifiableList() for new code. They are truly immutable (not backed by a mutable source), more concise, and often more memory-efficient.
Collections.synchronizedList() and related wrappers synchronise every individual method call, but compound operations (check-then-act, iteration) still require explicit synchronized blocks on the wrapper object. For new concurrent code prefer the java.util.concurrent collections:
// Collections.synchronized* — wrap any collection for thread-safety
// Every method is synchronized on the wrapper object
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
syncList.add("item1"); // thread-safe add
syncList.add("item2");
// CRITICAL: iteration must be manually synchronized!
synchronized (syncList) {
for (String s : syncList) { // safe — holds the lock
System.out.println(s);
}
}
// Without the synchronized block, another thread could modify syncList during iteration
// Other synchronized wrappers
Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
// Modern alternative — prefer java.util.concurrent classes for new code
// CopyOnWriteArrayList — thread-safe, iteration doesn't need external sync
// (good for read-heavy, infrequent-write scenarios)
import java.util.concurrent.*;
List<String> cowList = new CopyOnWriteArrayList<>();
cowList.add("a");
cowList.add("b");
for (String s : cowList) { // safe — iterates over a snapshot
System.out.println(s);
}
// ConcurrentHashMap — better than synchronizedMap for concurrent access
Map<String, Integer> concMap = new ConcurrentHashMap<>();
concMap.put("a", 1);
concMap.computeIfAbsent("b", k -> 2); // atomic operation
The synchronized wrappers from Collections require external synchronization for compound operations (check-then-act, iteration). For production concurrent code, use the dedicated classes from java.util.concurrent — ConcurrentHashMap, CopyOnWriteArrayList, BlockingQueue implementations — which are designed for concurrent use without external locking.