You've used two collections already: ArrayList for an ordered list and HashMap for key-value pairs. They're the ones you'll reach for most often, but they're not always the best fit. What if you need values kept in sorted order automatically? A waiting line where the first in is the first out? An "undo" history where the last action is undone first? A to-do list that always hands you the most urgent task?
Java's collections framework has a ready-made class for each of these, built on a few shared interfaces: List, Set, Queue and Map. Because they share interfaces, they also share many methods, so once you know one, the others feel familiar.
In this lesson you'll meet the main collection types, learn which to choose for a job, sort with Comparator, and use the Collections helper class.
The four families
| Interface | Holds | Allows duplicates | Main classes |
|---|---|---|---|
List | Items in order, by index | Yes | ArrayList, LinkedList |
Set | Unique items | No | HashSet, LinkedHashSet, TreeSet |
Queue / Deque | Items waiting to be processed | Yes | ArrayDeque, PriorityQueue |
Map | Key-value pairs | Unique keys | HashMap, LinkedHashMap, TreeMap |
Declare variables with the interface type and create them with a class: List<String> names = new ArrayList<>();. Your code then depends only on what a list can do, so you can switch to another class later by changing one word.
Three kinds of Set
All sets ignore duplicates, but they differ in the order you get back:
HashSet: no guaranteed order, the fastest.LinkedHashSet: remembers the order items were added.TreeSet: keeps items sorted, and adds methods likefirst(),last(),ceiling()andheadSet().
Maps come in the same three flavours: HashMap, LinkedHashMap and the sorted TreeMap. A sorted map is handy for things like scores by date, where you want to read the keys in order.
Queues, stacks and priority
A queue is first in, first out, like people waiting at a till. A stack is last in, first out, like a pile of plates. ArrayDeque (a double-ended queue) can be both:
| As a queue | As a stack |
|---|---|
offer(x) adds to the back | push(x) adds to the top |
poll() removes from the front | pop() removes from the top |
peek() looks at the front | peek() looks at the top |
A PriorityQueue always hands out the smallest item first (or the most important, with a custom order), whatever order items were added in. It's the classic tool for task schedulers and "next most urgent job".
Sorting with Comparator and the Collections class
list.sort() takes a Comparator that says how to order two items. Build one from a method reference:
people.sort(Comparator.comparing(Person::age));
people.sort(Comparator.comparing(Person::age).reversed());
people.sort(Comparator.comparing(Person::city).thenComparing(Person::name));The Collections class adds helpers: Collections.max(list), Collections.min(), Collections.shuffle(), Collections.frequency(list, x) and Collections.unmodifiableList(). List.of(...), Set.of(...) and Map.of(...) create small immutable collections, which throw an exception if you try to change them.
Performance at a glance
The right collection can make a program dramatically faster. Typical costs, using big-O notation where O(1) means "about the same time however large the collection" and O(n) means "time grows with the number of items":
| Collection | Add | Find by index | contains | Remove |
|---|---|---|---|---|
ArrayList | O(1) at the end | O(1) | O(n) | O(n) |
LinkedList | O(1) at either end | O(n) | O(n) | O(1) at either end |
HashSet / HashMap | O(1) | n/a | O(1) | O(1) |
TreeSet / TreeMap | O(log n) | n/a | O(log n) | O(log n) |
ArrayDeque | O(1) at either end | n/a | O(n) | O(1) at either end |
PriorityQueue | O(log n) | n/a | O(n) | O(log n) for the head |
The pattern to remember: lists are for order and position, hash-based collections are for fast lookups, and tree-based ones are for sorted data. The complexity lesson explains big-O notation, and the collections framework lesson maps out the interfaces in more detail.
Java Collections Syntax
Collections at a glance
List<String> list = new ArrayList<>();
Set<String> set = new TreeSet<>();
Map<String, Integer> map = new TreeMap<>();
Deque<String> stack = new ArrayDeque<>(); // push, pop, peek
Queue<String> queue = new ArrayDeque<>(); // offer, poll, peek
Queue<Integer> pq = new PriorityQueue<>();
list.sort(Comparator.comparing(Item::price).reversed());
Collections.max(list);
List<String> fixed = List.of("a", "b");Java Collections Summary
- List, Set, Queue and Map are the main collection interfaces; declare variables with the interface.
- HashSet is fastest, LinkedHashSet keeps insertion order and TreeSet keeps items sorted.
- ArrayDeque works as a queue (offer, poll) or a stack (push, pop); PriorityQueue gives the smallest first.
- Comparator.comparing, reversed() and thenComparing() describe sort orders.
- The Collections class has max, min, shuffle and frequency; List.of creates an immutable list.
Java Collections Examples
Each example shows the code, the output it produces and what each part does.
Three sets, three orders
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> visits = List.of("Leeds", "Lyon", "Leeds", "Porto", "Lisbon", "Lyon");
Set<String> hash = new HashSet<>(visits);
Set<String> linked = new LinkedHashSet<>(visits);
TreeSet<String> tree = new TreeSet<>(visits);
System.out.println("Unique cities: " + hash.size());
System.out.println("In visit order: " + linked);
System.out.println("Sorted: " + tree);
System.out.println("First alphabetically: " + tree.first());
System.out.println("Seen Paris? " + hash.contains("Paris"));
}
}Unique cities: 4
In visit order: [Leeds, Lyon, Porto, Lisbon]
Sorted: [Leeds, Lisbon, Lyon, Porto]
First alphabetically: Leeds
Seen Paris? falseLine-by-line explanation
- Line 5List.of creates a fixed list with two duplicates.
- Lines 7 to 9Each set drops the duplicates. Passing a collection to the constructor copies all its items.
- Line 12LinkedHashSet keeps the order of first appearance.
- Line 13TreeSet sorts the cities alphabetically.
A queue, a stack and a priority queue
import java.util.*;
public class Main {
public static void main(String[] args) {
Queue<String> till = new ArrayDeque<>();
till.offer("Ana");
till.offer("Ben");
till.offer("Cy");
System.out.println("Serving " + till.poll() + ", next is " + till.peek());
Deque<String> undo = new ArrayDeque<>();
undo.push("type 'Hello'");
undo.push("make bold");
undo.push("delete line");
System.out.println("Undo: " + undo.pop());
Queue<Integer> urgent = new PriorityQueue<>(List.of(5, 1, 4, 2));
System.out.print("By priority:");
while (!urgent.isEmpty()) {
System.out.print(" " + urgent.poll());
}
System.out.println();
}
}Serving Ana, next is Ben
Undo: delete line
By priority: 1 2 4 5Line-by-line explanation
- Lines 5 to 9A queue serves people in the order they arrived.
- Lines 11 to 15A stack undoes the most recent action first.
- Lines 17 to 21A priority queue always gives back the smallest number, whatever order they were added in.
Sorting objects with Comparator
import java.util.*;
public class Main {
record Player(String name, String team, int score) {}
public static void main(String[] args) {
List<Player> players = new ArrayList<>(List.of(
new Player("Ana", "Red", 120),
new Player("Ben", "Blue", 340),
new Player("Cleo", "Red", 340),
new Player("Dev", "Blue", 90)
));
players.sort(Comparator.comparing(Player::score).reversed()
.thenComparing(Player::name));
for (Player p : players) {
System.out.printf("%-5s %-5s %d%n", p.name(), p.team(), p.score());
}
Player top = Collections.max(players, Comparator.comparing(Player::score));
System.out.println("A top scorer: " + top.name());
}
}Ben Blue 340
Cleo Red 340
Ana Red 120
Dev Blue 90
A top scorer: BenLine-by-line explanation
- Line 4A record is a compact data class, covered in the records lesson.
- Lines 14 to 15Highest score first; players with the same score are ordered by name.
- Line 20Collections.max finds the largest by the given order. With a tie, it returns the first one it meets in the list.
Common Java Collections Mistakes
Expecting a HashSet to keep order
Set<String> steps = new HashSet<>(List.of("wash", "cut", "cook"));
System.out.println(steps); // order isn't guaranteedSet<String> steps = new LinkedHashSet<>(List.of("wash", "cut", "cook"));Changing a List.of list
List<String> days = List.of("Mon", "Tue");
days.add("Wed"); // UnsupportedOperationExceptionList<String> days = new ArrayList<>(List.of("Mon", "Tue"));
days.add("Wed");Using the old Stack class
Stack<String> history = new Stack<>();Deque<String> history = new ArrayDeque<>();Stack is an old, slower class; Java's documentation recommends ArrayDeque.
Java Collections in Real Projects
Collections in practice
Web servers queue incoming jobs in a Queue, editors keep undo history in a Deque, schedulers and route planners rely on PriorityQueue, and leaderboards keep scores in a TreeMap so the top entries are always ready. Choosing the right collection is one of the simplest ways to make Java code clearer and faster.
Java Collections FAQ
What is the difference between Collection and Collections?
Collection is the interface that List, Set and Queue extend; it declares shared methods such as add, remove, size and contains. Collections is a utility class full of static helper methods such as sort, max, shuffle and unmodifiableList.
Can Java collections hold primitive types?
No. Collections hold objects, so use wrapper types: List<Integer>, Set<Double>, Map<String, Boolean>. Java autoboxes values for you. For very large amounts of numeric data, a primitive array or an IntStream avoids the extra memory that wrapper objects use.
What is the difference between offer and add, or poll and remove, on a queue?
They do the same job but handle failure differently. add and remove throw an exception when they can't succeed, for example when a fixed-capacity queue is full or a queue is empty. offer returns false and poll returns null instead. With ArrayDeque and PriorityQueue, poll on an empty queue is the common case to handle.
How do I store my own objects in a TreeSet?
A TreeSet must be able to compare its items. Either make the class implement Comparable with a compareTo method, or pass a Comparator when you create the set: new TreeSet<>(Comparator.comparing(Person::name)). Without either, adding an item throws a ClassCastException.
Why shouldn't I use Vector, Stack or Hashtable?
They are older classes from Java 1.0 that synchronise every method, which makes them slower without making typical code correct across threads. Use ArrayList instead of Vector, ArrayDeque instead of Stack, and HashMap or ConcurrentHashMap instead of Hashtable.
Java Collections Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the line so the set keeps its items sorted.
Set<String> names = new <>();
Which collection rejects duplicate items?
What does this print?
Deque<Integer> d = new ArrayDeque<>();
d.push(1); d.push(2); d.push(3);
System.out.println(d.pop());
What does this print?
Queue<Integer> pq = new PriorityQueue<>(List.of(8, 3, 6));
System.out.println(pq.poll());
Which Comparator sorts players by score, highest first?
Java Collections Quiz
5 questions to check what you learned.
Why declare
List<String> names = new ArrayList<>();rather thanArrayList<String> names?- Code depends only on the List interface, so the class can be swapped easily
- It's faster
- ArrayList can't be a variable type
- It saves memory
Which collection keeps items in the order they were added, without duplicates?
- HashSet
- LinkedHashSet
- TreeSet
- PriorityQueue
Which method removes and returns the front of a Queue?
- peek()
- push()
- poll()
- get()
What does
Collections.frequency(list, "a")return?- Whether "a" is in the list
- The index of "a"
- A sorted list
- How many times "a" appears in the list
Which class does Java's documentation recommend instead of Stack?
- ArrayDeque
- LinkedList
- Vector
- PriorityQueue