Free Handbook · Every example compiled & verified

Collections

List, Set and Map in Java: ArrayList, HashSet, TreeMap and friends, sorting with Comparable and Comparator, the equals/hashCode contract, and picking the right one.

0 / 142 lessons🔥 0 day streak
ShareXLinkedIn

Module 08 · what you'll be able to do

  • Choose between List, Set and Map, and between their Array, Hash, Linked and Tree implementations
  • Add, look up, update and remove entries with the modern Map methods (getOrDefault, merge, computeIfAbsent)
  • Sort objects with Comparable for their natural order and Comparator for every other order
  • Explain the equals/hashCode contract and why breaking it makes HashSet and HashMap lose your objects
  • Remove items while iterating without triggering ConcurrentModificationException
01

The Collections Framework in one picture

An array has a fixed length chosen when you create it. Real programs rarely know that number in advance — a shopping cart grows, a set of logged-in users shrinks — so Java ships the Collections Framework in java.util: a small set of interfaces describing what a container can do, and several implementations of each, which differ in how they store data and therefore in what is fast.

Map is part of the framework but does not extend Collection — it holds pairs, not single elements.
InterfaceWhat it promisesMain implementations
List<E>Ordered by position, duplicates allowed, get(i)ArrayList, LinkedList
Set<E>No duplicatesHashSet, LinkedHashSet, TreeSet
Map<K,V>Unique keys, each mapped to one valueHashMap, LinkedHashMap, TreeMap
Queue<E> / Deque<E>Take from the head (and tail for Deque)ArrayDeque, PriorityQueue, LinkedList

The habit that separates juniors from everyone else: declare the variable with the interface type, create it with the implementation — List<String> names = new ArrayList<>();. The rest of the code only relies on "it is a List", so switching to a different implementation later is a one-word change. The empty <> (the diamond) tells the compiler to infer the type argument from the left-hand side.

javaMain.java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Ada");
        names.add("Linus");
        names.add("Ada");            // duplicates are fine in a List
        System.out.println(names + " size=" + names.size());

        Set<String> unique = new TreeSet<>(names);   // copy into a Set
        System.out.println(unique);

        Map<String, Integer> ages = new HashMap<>();
        ages.put("Ada", 36);
        System.out.println(ages.get("Ada") + " " + ages.get("Bob"));
    }
}
Outputcompiled & run with real Java
[Ada, Linus, Ada] size=3
[Ada, Linus]
36 null
Your turn

Add "Grace" to names before the copy and check that it lands in the right sorted spot in unique.

Immutable factory methods

List.of(...), Set.of(...) and Map.of(...) build small unmodifiable collections in one line. They are perfect for constants and test data, but any attempt to change them throws at runtime, and they reject null elements outright.

Error you will hit

UnsupportedOperationException: adding to List.of(...)

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> colors = List.of("red", "green");
        colors.add("blue");
        System.out.println(colors);
    }
}
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.ImmutableCollections.uoe(Unknown Source)
	at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(Unknown Source)
	at Main.main(Main.java:6)
Why the compiler said that

List.of returns an unmodifiable list. It still has an add method — every List must — but that method is implemented to throw. The compiler cannot tell the difference, so this is a runtime failure, not a compile error.

The fix

When you need to change the list, copy it into a mutable implementation: new ArrayList<>(List.of(...)).

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> colors = new ArrayList<>(List.of("red", "green"));
        colors.add("blue");
        System.out.println(colors);
    }
}
Error you will hit

unexpected type: List<int>

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<int> scores = new ArrayList<>();
    }
}
Main.java:5: error: unexpected type
        List<int> scores = new ArrayList<>();
             ^
  required: reference
  found:    int
1 error
Why the compiler said that

Generics only work with reference types (objects). int is a primitive, so it cannot be a type argument. Every primitive has a wrapper class — Integer, Long, Double, Boolean, Character — and Java converts between them automatically (autoboxing).

The fix

Use the wrapper type: List<Integer>. You can still write scores.add(90) and int s = scores.get(0).

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> scores = new ArrayList<>();
        scores.add(90);
        int first = scores.get(0);
        System.out.println(first);
    }
}
02

List: ArrayList and LinkedList

ArrayList keeps its elements in a plain array that it grows for you (by about 50% whenever it fills up). That makes get(i) and set(i, x) instant, and add at the end almost always instant. Inserting or removing near the front is the slow case: every later element shifts one slot.

javaMain.java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> tasks = new ArrayList<>();
        tasks.add("write tests");
        tasks.add("fix bug");
        tasks.add(0, "coffee");          // insert at index 0
        System.out.println(tasks);

        tasks.set(2, "fix bug #42");     // replace
        System.out.println(tasks.get(2));
        System.out.println(tasks.indexOf("fix bug #42") + " " + tasks.contains("deploy"));

        tasks.remove("coffee");          // remove by value
        for (int i = 0; i < tasks.size(); i++) {
            System.out.println(i + ": " + tasks.get(i));
        }
    }
}
Outputcompiled & run with real Java
[coffee, write tests, fix bug]
fix bug #42
2 false
0: write tests
1: fix bug #42

The remove(int) versus remove(Object) trap

List has two remove methods: remove(int index) and remove(Object o). With a List<Integer>, nums.remove(1) picks the int version — it removes whatever is at index 1, not the number 1. To remove by value, pass an Integer.

Visualizeremove(1) removes an index, not a valueStep 1 / 5
List<Integer> nums = new ArrayList<>(List.of(5, 1, 7, 1));
nums.remove(1);
System.out.println(nums);
nums.remove(Integer.valueOf(1));
System.out.println(nums);
Line 1

A mutable list of four boxed Integers. Index 0 holds 5, index 1 holds 1, index 2 holds 7, index 3 holds 1.

Variables now
nums[5, 1, 7, 1]
All 5 steps as a table
StepLineWhat happenedVariables now
11A mutable list of four boxed Integers. Index 0 holds 5, index 1 holds 1, index 2 holds 7, index 3 holds 1.nums = [5, 1, 7, 1]
22The literal 1 is an int, and an exact remove(int index) match beats a boxing conversion, so Java removes the element at index 1.nums = [5, 7, 1]
33Prints the list.nums = [5, 7, 1]
44Integer.valueOf(1) is an object, so this calls remove(Object): it removes the first element equal to 1.nums = [5, 7]
55Prints the list.nums = [5, 7]

LinkedList

LinkedList stores each element in its own node with pointers to the previous and next node. Adding or removing at either end is instant, but get(i) must walk node by node. In practice ArrayList wins almost every benchmark, even for middle inserts, because arrays sit together in memory and the CPU cache loves that. Reach for LinkedList rarely; if you need a queue or stack, ArrayDeque is faster.

javaMain.java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Deque<String> history = new ArrayDeque<>();
        history.push("home");            // stack: push/pop at the head
        history.push("search");
        history.push("product");
        System.out.println("back to: " + history.pop());
        System.out.println("now on:  " + history.peek());

        Queue<String> printJobs = new ArrayDeque<>();
        printJobs.offer("report.pdf");   // queue: in at the tail
        printJobs.offer("invoice.pdf");
        System.out.println("printing " + printJobs.poll());   // out at the head
        System.out.println("waiting: " + printJobs);
    }
}
Outputcompiled & run with real Java
back to: product
now on:  search
printing report.pdf
waiting: [invoice.pdf]
Do not use Stack or Vector
java.util.Stack and Vector are Java 1.0 leftovers that lock on every call. Use ArrayDeque for stacks and queues, ArrayList for lists.
03

Set: HashSet, LinkedHashSet and TreeSet

A Set refuses duplicates: add returns false and changes nothing if an equal element is already there. The three implementations differ only in iteration order and speed.

ImplementationIteration orderadd / contains
HashSetUnspecified — can change as the set growsO(1) on average
LinkedHashSetInsertion orderO(1) on average
TreeSetSorted (natural order or a Comparator)O(log n)
javaMain.java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        String[] visits = {"paris", "tokyo", "lima", "paris", "oslo", "tokyo"};

        Set<String> seen = new HashSet<>();
        for (String city : visits) {
            if (!seen.add(city)) System.out.println("repeat visit: " + city);
        }
        System.out.println("distinct: " + seen.size());

        System.out.println(new LinkedHashSet<>(List.of(visits)));   // first-seen order
        TreeSet<String> sorted = new TreeSet<>(List.of(visits));
        System.out.println(sorted);
        System.out.println(sorted.first() + " .. " + sorted.last());
        System.out.println("before 'oslo': " + sorted.headSet("oslo"));
    }
}
Outputcompiled & run with real Java
repeat visit: paris
repeat visit: tokyo
distinct: 4
[paris, tokyo, lima, oslo]
[lima, oslo, paris, tokyo]
lima .. tokyo
before 'oslo': [lima]
Your turn

Use sorted.ceiling("m") to find the first city alphabetically at or after "m".

Why the HashSet was never printed
Printing a HashSet shows whatever order its internal buckets happen to hold — it is not random, but it is not something you may rely on either, and it can change between Java versions or when the set resizes. If the order matters to a reader or a test, use LinkedHashSet or TreeSet.

Set algebra

javaMain.java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Set<String> alice = new TreeSet<>(Set.of("java", "sql", "docker"));
        Set<String> bob = new TreeSet<>(Set.of("java", "python", "sql"));

        Set<String> both = new TreeSet<>(alice);
        both.retainAll(bob);                 // intersection
        Set<String> either = new TreeSet<>(alice);
        either.addAll(bob);                  // union
        Set<String> onlyAlice = new TreeSet<>(alice);
        onlyAlice.removeAll(bob);            // difference

        System.out.println(both + " " + either + " " + onlyAlice);
    }
}
Outputcompiled & run with real Java
[java, sql] [docker, java, python, sql] [docker]
04

Map: HashMap, LinkedHashMap and TreeMap

A Map stores key → value pairs with unique keys. put on an existing key overwrites its value. get returns null for a missing key — which is why the modern helpers exist: they remove most of the null checks you would otherwise write.

MethodWhat it does
getOrDefault(k, d)Value for k, or d if k is absent
putIfAbsent(k, v)Only puts when k is absent
merge(k, v, fn)Puts v if absent, else stores fn(old, v) — the counting idiom
computeIfAbsent(k, fn)Creates the value on first use — the grouping idiom
entrySet()Iterate keys and values together
javaMain.java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        String text = "to be or not to be that is the question";

        Map<String, Integer> counts = new TreeMap<>();      // sorted keys
        for (String word : text.split(" ")) {
            counts.merge(word, 1, Integer::sum);           // +1, or start at 1
        }
        System.out.println(counts);
        System.out.println("be=" + counts.get("be") + " cat=" + counts.getOrDefault("cat", 0));

        Map<Integer, List<String>> byLength = new TreeMap<>();
        for (String word : counts.keySet()) {
            byLength.computeIfAbsent(word.length(), k -> new ArrayList<>()).add(word);
        }
        for (Map.Entry<Integer, List<String>> e : byLength.entrySet()) {
            System.out.println(e.getKey() + " letters: " + e.getValue());
        }
    }
}
Outputcompiled & run with real Java
{be=2, is=1, not=1, or=1, question=1, that=1, the=1, to=2}
be=2 cat=0
2 letters: [be, is, or, to]
3 letters: [not, the]
4 letters: [that]
8 letters: [question]
Your turn

Change counts to a LinkedHashMap and notice the words now print in first-seen order.

VisualizeHow merge counts wordsStep 1 / 8
Map<String, Integer> counts = new TreeMap<>();
for (String w : "a b a".split(" ")) {
counts.merge(w, 1, Integer::sum);
}
System.out.println(counts);
Line 1

An empty sorted map.

Variables now
counts{}
All 8 steps as a table
StepLineWhat happenedVariables now
11An empty sorted map.counts = {}
22First word: "a".w = "a"
33"a" is absent, so merge simply stores the given value 1.counts = {a=1}
42Next word: "b".w = "b"
53"b" is absent too — stored as 1.counts = {a=1, b=1}
62Next word: "a" again.w = "a"
73"a" is present, so merge calls Integer.sum(1, 1) and stores the result.counts = {a=2, b=1}
85TreeMap prints keys in sorted order.

TreeMap navigation

TreeMap keeps keys sorted, so it can answer "nearest key" questions that a HashMap cannot: floorKey, ceilingKey, firstEntry, headMap, tailMap. A classic use is mapping score thresholds to grades.

javaMain.java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        TreeMap<Integer, String> grades = new TreeMap<>();
        grades.put(0, "F");
        grades.put(50, "C");
        grades.put(70, "B");
        grades.put(90, "A");

        for (int score : new int[]{95, 70, 64, 12}) {
            System.out.println(score + " -> " + grades.floorEntry(score).getValue());
        }
    }
}
Outputcompiled & run with real Java
95 -> A
70 -> B
64 -> C
12 -> F
HashMap is the default in real code
Most production maps are HashMap — caches, lookups by id, JSON-like data. Pick LinkedHashMap when output order must be stable (API responses, reports), and TreeMap only when you need sorted keys or range queries.
05

Iterator and ConcurrentModificationException

The enhanced for loop over a collection is shorthand for an Iterator: hasNext(), next(), and optionally remove(). Every ArrayList, HashMap and friend keeps a hidden modification count. If the collection changes behind the iterator's back, the next next() call notices the count moved and throws ConcurrentModificationException — despite the name, no threads are needed.

Error you will hit

ConcurrentModificationException: removing inside a for-each loop

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> files = new ArrayList<>(List.of("a.tmp", "b.txt", "c.tmp", "d.txt"));
        for (String f : files) {
            if (f.endsWith(".tmp")) files.remove(f);
        }
        System.out.println(files);
    }
}
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(Unknown Source)
	at java.base/java.util.ArrayList$Itr.next(Unknown Source)
	at Main.main(Main.java:6)
Why the compiler said that

The for-each loop is driven by a hidden Iterator. files.remove(f) changes the list directly, bumping its modification count; on the next loop step the iterator compares counts, sees the list changed under it, and fails fast rather than silently skipping elements.

The fix

Remove through the iterator itself (it.remove()), or — simplest — use removeIf, which does the bookkeeping for you.

java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> files = new ArrayList<>(List.of("a.tmp", "b.txt", "c.tmp", "d.txt"));
        files.removeIf(f -> f.endsWith(".tmp"));
        System.out.println(files);
    }
}
javaMain.java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>(List.of(3, 8, 1, 6, 5));

        Iterator<Integer> it = nums.iterator();
        while (it.hasNext()) {
            int n = it.next();
            if (n % 2 == 0) it.remove();      // safe: the iterator removes
        }
        System.out.println(nums);

        Map<String, Integer> stock = new HashMap<>(Map.of("pen", 0, "ink", 4));
        stock.values().removeIf(q -> q == 0); // remove map entries by value
        System.out.println(stock);
    }
}
Outputcompiled & run with real Java
[3, 1, 5]
{ink=4}

Two safe ways to remove while iterating. removeIf is the one you will write most.

Rule of thumb
Never call add or remove on the collection you are looping over with for-each. Use removeIf, an explicit Iterator, or build a new collection.
06

Sorting: Comparable vs Comparator

To sort objects Java needs an answer to "which of these two comes first?". There are two ways to give it:

Comparable<T> — natural order

  • Implemented by the class itself: compareTo(T other)
  • One order per class (e.g. String sorts alphabetically, Integer numerically)
  • Used by Collections.sort(list), TreeSet, TreeMap with no extra argument

Comparator<T> — any other order

  • A separate object: compare(T a, T b)
  • As many orders as you like: by price, by name, descending…
  • Built fluently: Comparator.comparing(...).thenComparing(...).reversed()

Both return a negative number if the first comes before the second, zero if they are equal in that order, positive if it comes after. Never write return a.price - b.price for large or negative numbers — it can overflow. Use Integer.compare or Comparator.comparingInt.

javaMain.java
import java.util.*;

public class Main {
    record Book(String title, int year, double price) implements Comparable<Book> {
        public int compareTo(Book other) {          // natural order: by title
            return title.compareTo(other.title);
        }
    }

    public static void main(String[] args) {
        List<Book> books = new ArrayList<>(List.of(
            new Book("Refactoring", 1999, 47.5),
            new Book("Clean Code", 2008, 37.0),
            new Book("Effective Java", 2018, 45.0),
            new Book("Head First Java", 2003, 37.0)));

        Collections.sort(books);                              // Comparable
        books.forEach(b -> System.out.println(b.title()));

        books.sort(Comparator.comparingDouble(Book::price)    // Comparator
                             .thenComparing(Book::year, Comparator.reverseOrder()));
        books.forEach(b -> System.out.println(b.price() + " " + b.year() + " " + b.title()));
    }
}
Outputcompiled & run with real Java
Clean Code
Effective Java
Head First Java
Refactoring
37.0 2008 Clean Code
37.0 2003 Head First Java
45.0 2018 Effective Java
47.5 1999 Refactoring
Your turn

Sort the books newest first with Comparator.comparingInt(Book::year).reversed().

Error you will hit

ClassCastException: TreeSet of a class that is not Comparable

java
import java.util.*;

public class Main {
    record Point(int x, int y) {}

    public static void main(String[] args) {
        Set<Point> points = new TreeSet<>();
        points.add(new Point(2, 3));
        System.out.println(points);
    }
}
Exception in thread "main" java.lang.ClassCastException: class Main$Point cannot be cast to class java.lang.Comparable (Main$Point is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
	at java.base/java.util.TreeMap.compare(Unknown Source)
	at java.base/java.util.TreeMap.addEntryToEmptyMap(Unknown Source)
	at java.base/java.util.TreeMap.put(Unknown Source)
	at java.base/java.util.TreeMap.put(Unknown Source)
	at java.base/java.util.TreeSet.add(Unknown Source)
	at Main.main(Main.java:8)
Why the compiler said that

A TreeSet keeps elements sorted, so on the very first add it casts the element to Comparable to compare it. Point does not implement Comparable and no Comparator was given, so the cast fails at runtime.

The fix

Either implement Comparable<Point>, or pass a Comparator to the constructor.

java
import java.util.*;

public class Main {
    record Point(int x, int y) {}

    public static void main(String[] args) {
        Set<Point> points = new TreeSet<>(Comparator.comparingInt(Point::x).thenComparingInt(Point::y));
        points.add(new Point(2, 3));
        System.out.println(points);
    }
}

The Collections utility class

java.util.Collections (with an s) is a toolbox of static methods that work on any collection.

javaMain.java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> scores = new ArrayList<>(List.of(72, 95, 60, 95, 81));

        System.out.println("max=" + Collections.max(scores) + " min=" + Collections.min(scores));
        System.out.println("95 appears " + Collections.frequency(scores, 95) + " times");

        Collections.sort(scores);
        System.out.println(scores + " index of 81: " + Collections.binarySearch(scores, 81));
        Collections.reverse(scores);
        System.out.println(scores);
        Collections.swap(scores, 0, scores.size() - 1);
        System.out.println(scores);

        List<String> blanks = new ArrayList<>(Collections.nCopies(3, "-"));
        System.out.println(blanks);

        List<Integer> readOnly = Collections.unmodifiableList(scores);
        System.out.println(readOnly.get(0) + " (a read-only view)");
    }
}
Outputcompiled & run with real Java
max=95 min=60
95 appears 2 times
[60, 72, 81, 95, 95] index of 81: 2
[95, 95, 81, 72, 60]
[60, 95, 81, 72, 95]
[-, -, -]
60 (a read-only view)
unmodifiableList is a view, List.copyOf is a copy
Collections.unmodifiableList(x) wraps x: callers cannot change it, but if you change x they see the change. List.copyOf(x) takes an independent immutable snapshot.
07

The equals and hashCode contract

HashSet and HashMap find an object in two steps: hashCode() picks a bucket, then equals() checks the few objects in that bucket. The inherited versions from Object compare identity — two separately created objects are never equal, even with identical fields. So a class used as a key or set element must override both methods, following one rule:

The contract
If a.equals(b) is true, then a.hashCode() == b.hashCode() must be true. (The reverse need not hold — different objects may share a hash code.) Override equals without hashCode and equal objects land in different buckets, so the set never compares them.
javaMain.java
import java.util.*;

public class Main {
    static class PlainEmail {                    // no equals/hashCode
        final String address;
        PlainEmail(String a) { address = a; }
    }

    static class Email {                         // correct equals/hashCode
        final String address;
        Email(String a) { address = a; }
        @Override public boolean equals(Object o) {
            return o instanceof Email e && address.equals(e.address);
        }
        @Override public int hashCode() { return Objects.hash(address); }
    }

    record EmailRecord(String address) {}        // records generate both

    public static void main(String[] args) {
        Set<PlainEmail> a = new HashSet<>();
        a.add(new PlainEmail("[email protected]"));
        a.add(new PlainEmail("[email protected]"));
        System.out.println("plain: " + a.size());

        Set<Email> b = new HashSet<>();
        b.add(new Email("[email protected]"));
        b.add(new Email("[email protected]"));
        System.out.println("overridden: " + b.size());

        Set<EmailRecord> c = new HashSet<>();
        c.add(new EmailRecord("[email protected]"));
        System.out.println("record contains: " + c.contains(new EmailRecord("[email protected]")));
    }
}
Outputcompiled & run with real Java
plain: 2
overridden: 1
record contains: true

The same "duplicate" is stored twice until the class defines what equal means.

Two more rules that bite in production: fields used in equals/hashCode should be immutable — change a key's field after putting it in a HashMap and its hash no longer matches its bucket, so get cannot find it. And records give you correct, field-based equals, hashCode and toString for free, which is why they make ideal map keys.

javaMain.java
import java.util.*;

public class Main {
    static class Key {
        String id;
        Key(String id) { this.id = id; }
        @Override public boolean equals(Object o) { return o instanceof Key k && id.equals(k.id); }
        @Override public int hashCode() { return id.hashCode(); }
    }

    public static void main(String[] args) {
        Key k = new Key("A1");
        Map<Key, String> owners = new HashMap<>();
        owners.put(k, "Ada");

        k.id = "B2";                                   // mutate a key already in the map
        System.out.println(owners.get(k));             // wrong bucket
        System.out.println(owners.get(new Key("A1"))); // right bucket, but equals fails
        System.out.println(owners.size());             // the entry is still there, just lost
    }
}
Outputcompiled & run with real Java
null
null
1
08

Choosing the right collection

Nine times out of ten the answer is ArrayList, HashMap or HashSet. Ask these questions in order:

  1. 1
    Pairs or single values?

    Key → value lookups mean a Map. Otherwise keep going.

  2. 2
    Are duplicates meaningful?

    If "is X in here?" is the main question, or duplicates must be rejected, use a Set. Otherwise a List.

  3. 3
    Does order matter?

    None: Hash*. Insertion order: LinkedHash*. Sorted: Tree*.

  4. 4
    Is it a queue or stack?

    ArrayDeque; by priority, PriorityQueue.

  5. 5
    Shared between threads?

    ConcurrentHashMap, CopyOnWriteArrayList — see Module 10.

Big-O is covered properly in Module 13.
Collectionget / containsaddremoveOrder
ArrayListget(i) O(1), contains O(n)O(1) amortised at endO(n)Index
LinkedListO(n)O(1) at endsO(1) at endsIndex
ArrayDeque—O(1) at endsO(1) at endsInsertion
HashSet / HashMapO(1) averageO(1) averageO(1) averageNone
LinkedHashSet / LinkedHashMapO(1) averageO(1) averageO(1) averageInsertion
TreeSet / TreeMapO(log n)O(log n)O(log n)Sorted
PriorityQueuepeek O(1)O(log n)poll O(log n)Smallest first
javaMain.java
import java.util.*;

public class Main {
    record Job(String name, int priority) {}

    public static void main(String[] args) {
        PriorityQueue<Job> queue = new PriorityQueue<>(Comparator.comparingInt(Job::priority));
        queue.add(new Job("send newsletter", 3));
        queue.add(new Job("restart db", 1));
        queue.add(new Job("rotate logs", 2));

        while (!queue.isEmpty()) {
            System.out.println(queue.poll().name());   // lowest number first
        }
    }
}
Outputcompiled & run with real Java
restart db
rotate logs
send newsletter

A PriorityQueue always hands out the smallest element next — perfect for job schedulers.

Collections Framework
The java.util interfaces (List, Set, Map, Queue, Deque) and their implementations.
ArrayList
A List backed by a growable array: fast index access, slow front inserts.
HashMap
A Map using hashCode to pick a bucket and equals to confirm a key: O(1) average lookups, no order.
TreeMap
A Map kept sorted by key (a red-black tree): O(log n), supports floor/ceiling/range queries.
LinkedHashMap
A HashMap that also remembers insertion order.
Comparable
Interface a class implements to define its natural order via compareTo.
Comparator
A separate object defining an order via compare(a, b); composable with thenComparing.
equals/hashCode contract
Equal objects must have equal hash codes, or hash-based collections lose them.
ConcurrentModificationException
Thrown when a collection is structurally changed while an iterator is walking it.
Autoboxing
Automatic conversion between a primitive (int) and its wrapper (Integer).
Quick check

You need to count how often each word appears and print the words in alphabetical order. Which is the best fit?

Quick check

A class overrides equals but not hashCode. What happens when you add two equal instances to a HashSet?

Frequently asked questions

What is the difference between ArrayList and LinkedList in Java?
ArrayList stores elements in a resizable array, so get(i) is O(1) and it is cache-friendly. LinkedList stores separate nodes, so adding or removing at either end is O(1) but get(i) is O(n). In practice ArrayList is faster for almost everything; use ArrayDeque when you need a queue or stack.
When should I use HashMap vs TreeMap?
Use HashMap by default: O(1) average lookups with no ordering. Use TreeMap when you need keys sorted, or navigation such as floorKey, ceilingKey and range views, at O(log n) per operation. LinkedHashMap sits between them: HashMap speed plus insertion order.
How do I avoid ConcurrentModificationException?
Do not add to or remove from a collection while a for-each loop walks it. Use removeIf, remove through an explicit Iterator with it.remove(), or collect changes into a new collection. When several threads share a collection, use a concurrent one such as ConcurrentHashMap.

Finish the Java handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.