Java Collections Utility Class
Table of Contents + β
In the last lesson you learned about Java TreeMap. Java also ships a helper packed with ready-made tools for working with collections, so you do not write sorting or searching by hand. It is the Collections utility class (note the capital C), with static methods like sort, reverse, and max.
π€ Why a utility class?
Think of Collections like the drawer of kitchen tools next to your stove. You do not carve your own knife every time you cook. You reach for the one that is already there, already sharp, already tested. The Collections class is that drawer for lists, sets, and other collections.
What you gain by using it:
- You write less code, so there is less to get wrong.
- The methods are battle-tested by millions of programs, so bugs are rare.
- Your code reads clearly.
Collections.sort(list)says exactly what it does.
Collections vs Collection
There are two names that look almost the same. Collection (singular, no s) is the interface that lists, sets, and queues implement. Collections (plural, with s) is the utility class full of static helper methods. This whole lesson is about Collections, the toolbox. Keep this clear from the very start. It is the single most common mix-up in this topic.
π’ Sorting a list
The method you will reach for most is sort. It puts a list in natural order, which means numbers go from small to large and Strings go in dictionary order (A to Z). You need to import java.util.Collections.
This first example sorts a small list of numbers that start out jumbled.
import java.util.ArrayList;import java.util.Collections;
public class Main { public static void main(String[] args) { ArrayList<Integer> numbers = new ArrayList<>(); numbers.add(50); numbers.add(10); numbers.add(30);
Collections.sort(numbers); // β
sorts in place, ascending System.out.println(numbers); }}What that line does:
- It sorts the list in place. It rearranges the same list object.
- It does not give you a new list back, so
sortreturns nothing. - After the call, the values sit in ascending order.
Output
[10, 30, 50]You can also sort with your own rule by passing a Comparator. A Comparator is a small object that tells sort how to compare two items. Here we sort names by length instead of alphabetically.
import java.util.ArrayList;import java.util.Collections;import java.util.Comparator;
public class Main { public static void main(String[] args) { ArrayList<String> names = new ArrayList<>(); names.add("Alex"); names.add("Riya"); names.add("Jo");
// β
sort by name length, shortest first Collections.sort(names, Comparator.comparingInt(String::length)); System.out.println(names); }}How the comparator changes things:
Comparator.comparingInt(String::length)says βcompare names by length.β- So βJoβ (2 letters) comes before βAlexβ and βRiyaβ (4 letters each).
- Without a comparator,
sortwould use natural A to Z order instead.
Output
[Jo, Alex, Riya]π Reverse and sort descending
reverse flips a list end to end. To sort descending you have two options:
- Sort first, then reverse.
- Or pass a reverse-order comparator straight to
sort. This way is cleaner.
This example shows both ways side by side.
import java.util.ArrayList;import java.util.Collections;
public class Main { public static void main(String[] args) { ArrayList<Integer> numbers = new ArrayList<>(); numbers.add(50); numbers.add(10); numbers.add(30);
Collections.sort(numbers); // [10, 30, 50] Collections.reverse(numbers); // [50, 30, 10] System.out.println("Reversed: " + numbers);
// β
or directly with a reverse-order comparator: Collections.sort(numbers, Collections.reverseOrder()); System.out.println("Descending: " + numbers); }}What each part does:
reverseflips the order of the elements. It does not sort. It just turns the list back to front.reverseOrder()is a built-in comparator meaning βgo from high to low.β Passing it tosortgives a descending list in one step.- Both paths reach the same result. The comparator way is shorter and clearer.
Output
Reversed: [50, 30, 10]Descending: [50, 30, 10]π max, min, and frequency
These methods answer common questions about a collection without you writing a loop.
max(list)returns the largest element.min(list)returns the smallest element.frequency(list, value)counts how many times a value appears.
This example asks all three questions about one list of numbers.
import java.util.ArrayList;import java.util.Collections;
public class Main { public static void main(String[] args) { ArrayList<Integer> numbers = new ArrayList<>(); numbers.add(10); numbers.add(40); numbers.add(20); numbers.add(40);
System.out.println("Max: " + Collections.max(numbers)); System.out.println("Min: " + Collections.min(numbers)); System.out.println("Count of 40: " + Collections.frequency(numbers, 40)); }}What the calls do:
maxandminscan the whole list and hand back the biggest and smallest values.frequency(numbers, 40)counts how many elements equal 40, which is 2 here.- These replace the find-max and counting loops you wrote by hand before.
Output
Max: 40Min: 10Count of 40: 2Watch out with max and min:
- An empty list has no biggest or smallest value.
- So Java throws a
NoSuchElementException. - Always check the list has at least one element first.
π binarySearch (the list must be sorted)
binarySearch finds the position of a value very fast. It has one strict rule:
- The list must already be sorted before you search.
- On an unsorted list the answer is wrong or meaningless.
- It works by jumping to the middle, then throwing away half the list each step.
- That trick only works if the list is in order, like flipping to the middle of a dictionary.
This example sorts first, then searches.
import java.util.ArrayList;import java.util.Collections;
public class Main { public static void main(String[] args) { ArrayList<Integer> numbers = new ArrayList<>(); numbers.add(50); numbers.add(10); numbers.add(30);
Collections.sort(numbers); // β
[10, 30, 50] first int index = Collections.binarySearch(numbers, 30); System.out.println("Found 30 at index: " + index); }}What happens here:
- After sorting, the list is
[10, 30, 50], so 30 sits at index 1. binarySearchreturns that index.- If the value is not found, it returns a negative number instead of an error.
- So a result of 0 or higher means βfound.β
Output
Found 30 at index: 1π swap, fill, copy, addAll, nCopies, and emptyList
A handful of smaller helpers round out the toolbox. Each does one focused job:
swap(list, i, j)exchanges the elements at two positions.fill(list, value)replaces every element with the same value.copy(dest, src)copies the source list into the destination. The destination must already be at least as big as the source.addAll(list, items...)adds several items at once.nCopies(n, value)builds a read-only list with the same value repeatedntimes.emptyList()gives you an empty, read-only list, handy as a safe default.
This example uses several of them together.
import java.util.ArrayList;import java.util.Collections;import java.util.List;
public class Main { public static void main(String[] args) { ArrayList<String> seats = new ArrayList<>(); Collections.addAll(seats, "A", "B", "C"); // add many at once System.out.println("Seats: " + seats);
Collections.swap(seats, 0, 2); // swap A and C System.out.println("After swap: " + seats);
Collections.fill(seats, "empty"); // every slot becomes "empty" System.out.println("After fill: " + seats);
List<String> threes = Collections.nCopies(3, "x"); // read-only list of three "x" System.out.println("nCopies: " + threes);
List<String> nothing = Collections.emptyList(); // safe empty default System.out.println("emptyList: " + nothing); }}Worth noting from the run:
fillkeeps the size the same but makes every value match.nCopies(3, "x")is handy for a repeated default, like three blank rows.emptyList()is a clean way to return βnothingβ without usingnull.
Output
Seats: [A, B, C]After swap: [C, B, A]After fill: [empty, empty, empty]nCopies: [x, x, x]emptyList: []nCopies and emptyList are read-only
Both nCopies and emptyList give you a list you cannot change. If you try to add to them or set a value, Java throws an UnsupportedOperationException. If you need a changeable list, wrap them like new ArrayList<>(Collections.nCopies(3, "x")).
π² shuffle a list
shuffle randomly reorders a list. It is perfect for card games, quizzes, or picking a random order, so you never write your own shuffle.
This example shuffles a small deck of cards.
import java.util.ArrayList;import java.util.Collections;
public class Main { public static void main(String[] args) { ArrayList<String> cards = new ArrayList<>(); cards.add("Ace"); cards.add("King"); cards.add("Queen");
Collections.shuffle(cards); // β
random order System.out.println("Shuffled: " + cards); }}A note on the output:
shufflemixes the list into a random order each run.- Because the order is random, your output will differ from the sample below.
- It will likely change every run. That is expected.
Output
Shuffled: [King, Ace, Queen]π Read-only and thread-safe wrappers
Two more tools change how a list behaves rather than its contents. First, unmodifiableList:
- It gives you a read-only view of a list.
- Reading from it is fine.
- Any attempt to change it throws an
UnsupportedOperationException. - Use it to hand a list out and promise βyou can look, but you cannot touch.β
This example creates a read-only view and then tries to change it.
import java.util.ArrayList;import java.util.Collections;import java.util.List;
public class Main { public static void main(String[] args) { ArrayList<String> menu = new ArrayList<>(); menu.add("Tea"); menu.add("Coffee");
List<String> readOnly = Collections.unmodifiableList(menu); System.out.println("Read: " + readOnly.get(0)); // β
reading is fine
readOnly.add("Juice"); // β throws UnsupportedOperationException }}What happens here:
- The
getcall works because reading is always allowed. - The
addcall fails because the view is locked. - Java stops the program with an error instead of quietly changing the list.
Output
Read: TeaException in thread "main" java.lang.UnsupportedOperationExceptionNext, synchronizedList:
- It wraps a list so it is safe to use from several threads at once.
- Without it, two threads changing the same list can corrupt it.
- The wrapper adds a lock so only one thread touches the list at a time.
This example wraps a list for thread-safe use.
import java.util.ArrayList;import java.util.Collections;import java.util.List;
public class Main { public static void main(String[] args) { List<String> safe = Collections.synchronizedList(new ArrayList<>()); safe.add("Alex"); // β
safe to call from multiple threads System.out.println(safe); }}You will care about this once you reach the multithreading topic. For now, just remember synchronizedList exists for when many threads share one list.
Output
[Alex]π§ͺ Worked example: sort then search a scoreboard
Letβs put the pieces together. The plan: sort the player names first, then use binarySearch to find one. Remember, binarySearch only works on a sorted list.
This program builds a list of players, sorts it, and searches for one name.
import java.util.ArrayList;import java.util.Collections;
public class Main { public static void main(String[] args) { ArrayList<String> players = new ArrayList<>(); Collections.addAll(players, "Riya", "Alex", "Sam", "Maya");
Collections.sort(players); // β
must sort before searching System.out.println("Sorted: " + players);
int index = Collections.binarySearch(players, "Sam"); if (index >= 0) { System.out.println("Sam is at index: " + index); } else { System.out.println("Sam is not in the list."); }
System.out.println("First in order: " + Collections.min(players)); System.out.println("Last in order: " + Collections.max(players)); }}The flow:
addAllloads four names in one call.sortputs them in alphabetical order, whichbinarySearchneeds.binarySearchreturns the index of βSam.β Since it is 0 or higher, we know the name was found.minandmaxreport the first and last names in alphabetical order without a loop.
Output
Sorted: [Alex, Maya, Riya, Sam]Sam is at index: 3First in order: AlexLast in order: Samπ Collections.sort vs List.sort
You may see two ways to sort a list, and both are fine:
Collections.sort(list)is the older static helper in theCollectionsclass.list.sort(null)orlist.sort(comparator)is built into theListinterface (Java 8).- They do the same job.
Collections.sort(list)just callslist.sort(null)for you. - Use
list.sort(comparator)when you have the list in hand, likeplayers.sort(Comparator.naturalOrder()). - Use
Collections.sort(list)if you prefer the helper style or work in older code.
Either way the result is the same sorted list. Pick whichever reads more clearly.
β οΈ Common Mistakes
A few Collections slip-ups trip people up often.
Running binarySearch on an unsorted list. The list must be sorted first, or the result is wrong.
// β Wrong: searching a list that is not sortedArrayList<Integer> data = new ArrayList<>();Collections.addAll(data, 50, 10, 30);int i = Collections.binarySearch(data, 30); // result is unreliable
// β
Right: sort first, then searchCollections.sort(data); // [10, 30, 50]int j = Collections.binarySearch(data, 30); // correct indexTrying to change an unmodifiable list. A read-only view rejects every change.
List<String> view = Collections.unmodifiableList(menu);view.add("Juice"); // β throws UnsupportedOperationException
// β
change the original list instead, then make a fresh viewmenu.add("Juice");List<String> view2 = Collections.unmodifiableList(menu);Confusing Collections with Collection. They look alike but are not the same.
// β Wrong: Collection (no s) has no static sort methodCollection.sort(list);
// β
Right: Collections (with s) is the utility classCollections.sort(list);β Best Practices
Habits that make Collections work well for you:
- Reach for it instead of writing your own loops. Sorting, searching, counting, and finding max or min are already done for you.
- Remember
sortis in place. It changes the original list and returns nothing. Do not expect a new list back. - Always sort before
binarySearch. It is the one rule the method cannot work without. - Use
reverseOrder()for descending sorts. It is cleaner than sort-then-reverse. - Guard against empty collections.
maxandminthrow an exception when there is nothing to compare. - Wrap lists you share. Use
unmodifiableListto share a read-only view andsynchronizedListwhen many threads touch one list.
π§© What Youβve Learned
Great, that completes the Collections module. Letβs recap the utility class.
- β
The
Collectionsutility class (with s) holds ready-made static methods for collections, separate from theCollectioninterface. - β
sortorders a list in place, takes an optionalComparator, andreverseflips it;reverseOrder()sorts descending. - β
max,min, andfrequencyanswer common questions without a loop;binarySearchfinds a value fast but needs a sorted list. - β
swap,fill,copy,addAll,nCopies,emptyList, andshufflehandle smaller everyday jobs. - β
unmodifiableListmakes a read-only view (changing it throwsUnsupportedOperationException) andsynchronizedListmakes one safe across threads.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What does Collections.sort(list) do?
Why: Collections.sort sorts the given list in place and returns nothing.
- 2
What is the difference between Collections and Collection?
Why: Collections (with s) is the helper class; Collection (no s) is the interface lists and sets implement.
- 3
How do you sort a list in descending order most cleanly?
Why: Passing Collections.reverseOrder() to sort orders the list high to low directly.
- 4
What must be true before you call Collections.binarySearch on a list?
Why: binarySearch only works correctly on a list that is already sorted; otherwise the result is unreliable.
π Whatβs Next?
Now that you can sort with a built-in comparator, the next step is teaching your own classes how to be sorted. That is what the Comparable interface does. Letβs learn how to define a natural order for your own objects.