Java Collections Utility Class: sort, shuffle, unmodifiable, and more

java.util.Collections (note: not the Collection interface) is a utility class full of static methods for manipulating collections: sorting, searching, shuffling, reversing, finding min/max, counting, rotating, and creating various wrappers. These utilities prevent you from reinventing common operations and make intent clear. Knowing the full palette saves time and produces more readable code.

Contents

sort, reverse, binarySearch

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:

Java
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, fill, copy, nCopies

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:

Java
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]

min, max, frequency, disjoint

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:

Java
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)

Unmodifiable Wrappers

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+):

Java
// 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.

Synchronized Wrappers

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:

Java
// 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.