📊
Collections

Advanced Collections

Comparator, Queue & TreeMap
💡 Comparable is when something tells you itself "here's how I sort" (like kids in a line whose height is built-in). Comparator is an outside teacher who decides a custom order. Queue is like a line (first come, first served).

Comparable (the compareTo method) is defined inside the class — it states the "natural ordering". Comparator (the compare method) provides a custom ordering from outside, as many as you like.

Queue follows FIFO (First-In-First-Out) order — like a ticket line. Deque can add/remove from both ends.

TreeMap/TreeSet automatically keep their keys in sorted order (unlike HashMap/HashSet, where order isn't guaranteed).

List<String> names = new ArrayList<>(List.of("Zoya","Aman","Ravi"));
names.sort(Comparator.naturalOrder());
// [Aman, Ravi, Zoya]

Queue<Integer> q = new LinkedList<>();
q.add(1); q.add(2);
System.out.println(q.poll()); // 1
📊
Comparable is when something tells you itself "here's how I sort" (like kids in a line whose height is built-in). Comparator is an outside teacher who decides a custom order. Queue is like a line (first come, first served).
1 / 5
⚡ Quick Recap
  • Comparable = a class's own order (compareTo)
  • Comparator = a custom order (compare)
  • Queue = FIFO, TreeMap = auto-sorted keys
On this page (5 subtopics)

By implementing the Comparable<T> interface, a class defines its own "natural order" — the compareTo() method returns negative/zero/positive to say the current object is smaller/equal/bigger than the other one (convention: negative = "smaller than me", positive = "bigger than me", 0 = "equal").

Collections.sort() or TreeSet automatically use this order when you don't provide a different Comparator. A class can have only ONE natural order (the Comparable interface is implemented just once) — if you need multiple orderings, use Comparator.

class Student implements Comparable<Student> {
  int marks;
  public int compareTo(Student other) {
    return this.marks - other.marks; // ascending order by marks
  }
}
⚠️Common Mistake: this.marks - other.marks carries an integer overflow risk for very large/negative numbers (an edge case). Safer: use Integer.compare(this.marks, other.marks).

Comparator lets you provide a custom ordering from outside the class, and build multiple different orderings for the same data (like sorting students sometimes by marks, sometimes by name) — without modifying the Student class. In Java 8+, you can write it in one line with a lambda, or use the fluent Comparator.comparing() API.

List<Student> students = getStudents();
students.sort((a, b) -> b.marks - a.marks); // descending
students.sort(Comparator.comparing(s -> s.name)); // by name
students.sort(Comparator.comparing((Student s) -> s.marks).reversed());

A PriorityQueue is a special Queue where elements come out not in insertion order, but based on "priority" (natural order, or a custom Comparator) — the smallest (or whatever the Comparator defines) always gets poll()ed first. Internally it uses a "heap" data structure.

Used in task scheduling (most urgent task first), Dijkstra's shortest-path algorithm, and "top-K elements" style problems.

PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.add(5); pq.add(1); pq.add(3);
System.out.println(pq.poll()); // 1 (sabse chhota pehle)

Both TreeMap and TreeSet use a Red-Black Tree (a self-balancing binary tree) internally, so elements/keys are always traversed in sorted order, and operations take O(log n) time (a bit slower than HashMap's O(1), but you get sorted order for free).

They come with extra bonus methods that HashMap doesn't have: firstKey(), lastKey(), higherKey(x) (the smallest key bigger than x), floorKey(x), and so on — very useful for range-based queries.

A Deque (Double-Ended Queue) can add/remove from both ends — addFirst/addLast, removeFirst/removeLast. This lets it simulate both a Stack (LIFO — push/pop, both from the front) and a Queue (FIFO — offer/poll).

In modern Java, ArrayDeque is recommended over the old Stack class (which extends Vector, carrying legacy and thread-safety overhead) — it's faster and has a cleaner API.

Deque<Integer> stack = new ArrayDeque<>();
stack.push(1); stack.push(2);
System.out.println(stack.pop()); // 2 (LIFO)