Collections Class
java.util.Collections (with an s) is a utility class of static methods that operate on or return collections. It provides ready-made algorithms (sorting, searching, shuffling, reversing, finding the minimum or maximum), factory methods for special collections (empty, single-element, repeated), and wrappers that make a collection unmodifiable, synchronized, or type-checked. It is distinct from Collection (no s), the root interface that List, Set, and Queue extend.
Collection | Collections | |
|---|---|---|
| Kind | Interface | Final utility class with a private constructor |
| Purpose | Defines what a collection is | Static helper methods that work on collections |
| Example | Collection<String> c = new ArrayList<>(); | Collections.sort(list); |
The Arrays class plays the same role for arrays.
Sorting
import java.util.*;
List<Integer> scores = new ArrayList<>(List.of(72, 95, 58, 88));
Collections.sort(scores); // [58, 72, 88, 95]
Collections.sort(scores, Comparator.reverseOrder()); // [95, 88, 72, 58]
Collections.sort(list) requires the elements to be Comparable; the two-argument form takes a Comparator. Both sort in place, stably. Since Java 8 they simply call list.sort(...), which is the more common style today. The list must be modifiable: sorting List.of(...) throws UnsupportedOperationException.
Searching, Minimum, Maximum, and Frequency
List<Integer> n = new ArrayList<>(List.of(5, 3, 8, 1));
Collections.sort(n); // [1, 3, 5, 8]
int pos = Collections.binarySearch(n, 5); // 2 (list must be sorted)
int low = Collections.min(n); // 1
int high = Collections.max(n); // 8
List<String> words = List.of("to", "be", "or", "not", "to", "be");
int count = Collections.frequency(words, "to"); // 2
String longest = Collections.max(words, Comparator.comparingInt(String::length)); // "not"
| Method | Purpose |
|---|---|
binarySearch(list, key) / (list, key, cmp) | Search a sorted list; same negative "insertion point" convention as Arrays.binarySearch |
min(coll) / max(coll) | Smallest / largest by natural order |
min(coll, cmp) / max(coll, cmp) | By a comparator |
frequency(coll, o) | Number of elements equal to o |
disjoint(c1, c2) | true if the collections share no elements |
indexOfSubList(source, target) | Position of a sublist, or -1 |
min and max work on any Collection, not just lists, and throw NoSuchElementException if it is empty.
Rearranging Lists
List<Integer> n = new ArrayList<>(List.of(1, 3, 5, 8));
Collections.reverse(n); // [8, 5, 3, 1]
Collections.swap(n, 0, 3); // [1, 5, 3, 8]
Collections.rotate(n, 1); // [8, 1, 5, 3] every element moves one place right
Collections.shuffle(n); // random order
Collections.fill(n, 0); // [0, 0, 0, 0]
| Method | Effect |
|---|---|
reverse(list) | Reverse in place |
shuffle(list) / shuffle(list, random) | Random permutation; pass a seeded Random for repeatable results |
swap(list, i, j) | Exchange two positions |
rotate(list, distance) | Shift elements; the ones pushed off one end wrap to the other |
fill(list, value) | Replace every element |
copy(dest, src) | Copy src into the start of dest (dest must already be large enough) |
rotate combined with subList moves a block of elements, for example Collections.rotate(list.subList(2, 6), -1) shifts the elements at indexes 2–5 one place left.
Creating Special Collections
Empty collections
List<String> none = Collections.emptyList();
Map<String, Integer> nothing = Collections.emptyMap();
Set<String> noneSet = Collections.emptySet();
These are shared, immutable singletons. Returning one instead of null lets callers loop over the result without a null check. List.of(), Set.of(), and Map.of() serve the same purpose in modern code.
Single-element and repeated collections
Set<String> admins = Collections.singleton("root"); // immutable set of one
List<String> one = Collections.singletonList("only");
Map<String, Integer> pair = Collections.singletonMap("timeout", 30);
List<String> dashes = Collections.nCopies(5, "-"); // [-, -, -, -, -]
String rule = String.join("", Collections.nCopies(20, "=")); // "===================="
nCopies returns an immutable list that stores the element once, so even nCopies(1_000_000, x) uses almost no memory. Wrapping it in new ArrayList<>(...) gives a modifiable list pre-filled with a value.
Adding several elements
List<String> langs = new ArrayList<>();
Collections.addAll(langs, "Java", "Bash", "C");
addAll with varargs avoids creating a temporary list.
Wrappers
Wrapper methods return a new view around an existing collection, adding a behavior without copying the data.
Unmodifiable views
List<String> internal = new ArrayList<>(List.of("a", "b"));
List<String> readOnly = Collections.unmodifiableList(internal);
readOnly.add("c"); // UnsupportedOperationException
internal.add("c");
System.out.println(readOnly); // [a, b, c]: the view reflects the backing list
Variants exist for every collection type: unmodifiableSet, unmodifiableMap, unmodifiableCollection, unmodifiableSortedMap, and others. Use a view when callers should see live data but must not change it. Use List.copyOf when they should get an independent snapshot.
Synchronized wrappers
List<String> shared = Collections.synchronizedList(new ArrayList<>());
Each method call on the wrapper is guarded by a lock. Iteration is not automatically safe: you must hold the lock for the whole loop:
synchronized (shared) {
for (String s : shared) {
process(s);
}
}
In modern code, purpose-built concurrent collections (ConcurrentHashMap, CopyOnWriteArrayList, ConcurrentLinkedQueue) are usually better choices than synchronized wrappers. They are covered in the concurrency section.
Checked wrappers
List<String> safe = Collections.checkedList(new ArrayList<>(), String.class);
A checked wrapper verifies the type of every element added, at runtime. It helps track down bugs where legacy code using raw types inserts the wrong kind of object into a generic collection: the error occurs at the point of insertion rather than later, when the element is read.
Reverse-Order Comparators
List<String> names = new ArrayList<>(List.of("Linus", "Ada", "Grace"));
names.sort(Collections.reverseOrder()); // [Linus, Grace, Ada]
names.sort(Collections.reverseOrder(String.CASE_INSENSITIVE_ORDER));
Collections.reverseOrder() is equivalent to Comparator.reverseOrder().
Collections Versus Newer APIs
Many Collections methods now have more modern equivalents. Both styles are common in real code:
| Task | Collections | Modern alternative |
|---|---|---|
| Sort a list | Collections.sort(list) | list.sort(null) |
| Empty immutable list | Collections.emptyList() | List.of() |
| Immutable copy | Collections.unmodifiableList(new ArrayList<>(x)) | List.copyOf(x) |
| Maximum | Collections.max(list) | list.stream().max(...) |
| Reverse view | (none) | list.reversed() (Java 21) |
The algorithms with no direct replacement, such as shuffle, rotate, swap, frequency, disjoint, and nCopies, remain the standard way to perform those operations.
Practical Example: Weighted Raffle
Students earn raffle tickets for completed lessons. The program builds the ticket list, shuffles it, draws three distinct winners, and exposes a read-only participant list.
import java.util.*;
public class Raffle {
public static void main(String[] args) {
List<String> entries = new ArrayList<>();
entries.addAll(Collections.nCopies(3, "ada")); // 3 tickets
entries.addAll(Collections.nCopies(1, "linus"));
entries.addAll(Collections.nCopies(2, "grace"));
Collections.addAll(entries, "alan", "margaret");
System.out.println("Tickets: " + entries.size());
System.out.println("Ada's tickets: " + Collections.frequency(entries, "ada"));
Collections.shuffle(entries, new Random(2026));
System.out.println("Draw order: " + entries);
Set<String> winners = new LinkedHashSet<>();
for (String name : entries) {
if (winners.size() == 3) break;
winners.add(name);
}
System.out.println("Winners: " + winners);
List<String> unique = new ArrayList<>(new TreeSet<>(entries));
System.out.println("Participants: " + unique);
System.out.println("Alphabetically last: " + Collections.max(unique));
List<String> readOnly = Collections.unmodifiableList(unique);
try {
readOnly.add("hacker");
} catch (UnsupportedOperationException e) {
System.out.println("Cannot modify the participant list");
}
}
}
Output:
Tickets: 8
Ada's tickets: 3
Draw order: [alan, ada, grace, linus, ada, ada, margaret, grace]
Winners: [alan, ada, grace]
Participants: [ada, alan, grace, linus, margaret]
Alphabetically last: margaret
Cannot modify the participant list
nCopies adds several tickets for the same student in one call, and addAll adds single entries without building a temporary list. frequency counts a student's tickets. shuffle with a seeded Random produces the same draw on every run, which is useful for testing or for publishing a verifiable draw; remove the seed (or use SecureRandom for anything where fairness must be guaranteed) for a truly unpredictable result. A LinkedHashSet keeps the first three distinct names in draw order, so a student with several tickets cannot win twice. Finally, unmodifiableList hands out the participant list safely.