Java Collections Utility Class

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 sort returns 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, sort would 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:

  • reverse flips 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 to sort gives 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:

  • max and min scan 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: 40
Min: 10
Count of 40: 2

Watch 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.
  • binarySearch returns 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 repeated n times.
  • 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:

  • fill keeps 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 using null.

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:

  • shuffle mixes 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 get call works because reading is always allowed.
  • The add call fails because the view is locked.
  • Java stops the program with an error instead of quietly changing the list.

Output

Read: Tea
Exception in thread "main" java.lang.UnsupportedOperationException

Next, 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:

  • addAll loads four names in one call.
  • sort puts them in alphabetical order, which binarySearch needs.
  • binarySearch returns the index of β€œSam.” Since it is 0 or higher, we know the name was found.
  • min and max report the first and last names in alphabetical order without a loop.

Output

Sorted: [Alex, Maya, Riya, Sam]
Sam is at index: 3
First in order: Alex
Last 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 the Collections class.
  • list.sort(null) or list.sort(comparator) is built into the List interface (Java 8).
  • They do the same job. Collections.sort(list) just calls list.sort(null) for you.
  • Use list.sort(comparator) when you have the list in hand, like players.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 sorted
ArrayList<Integer> data = new ArrayList<>();
Collections.addAll(data, 50, 10, 30);
int i = Collections.binarySearch(data, 30); // result is unreliable
// βœ… Right: sort first, then search
Collections.sort(data); // [10, 30, 50]
int j = Collections.binarySearch(data, 30); // correct index

Trying 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 view
menu.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 method
Collection.sort(list);
// βœ… Right: Collections (with s) is the utility class
Collections.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 sort is 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. max and min throw an exception when there is nothing to compare.
  • Wrap lists you share. Use unmodifiableList to share a read-only view and synchronizedList when many threads touch one list.

🧩 What You’ve Learned

Great, that completes the Collections module. Let’s recap the utility class.

  • βœ… The Collections utility class (with s) holds ready-made static methods for collections, separate from the Collection interface.
  • βœ… sort orders a list in place, takes an optional Comparator, and reverse flips it; reverseOrder() sorts descending.
  • βœ… max, min, and frequency answer common questions without a loop; binarySearch finds a value fast but needs a sorted list.
  • βœ… swap, fill, copy, addAll, nCopies, emptyList, and shuffle handle smaller everyday jobs.
  • βœ… unmodifiableList makes a read-only view (changing it throws UnsupportedOperationException) and synchronizedList makes one safe across threads.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What does Collections.sort(list) do?

    Why: Collections.sort sorts the given list in place and returns nothing.

  2. 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. 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. 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.

Java Comparable Interface

Share & Connect