Comparable
Comparable
Comparable<T> defines the natural ordering of a class. It has a single method compareTo(T o) that returns a negative integer, zero, or positive integer to indicate ordering.
Contract:
x.compareTo(y)returns negative if x < y- Returns 0 if x == y
- Returns positive if x > y
- Must be consistent with equals()
- Transitive: if x < y and y < z, then x < z
public class Student implements Comparable<Student> {
private String name;
private int age;
private double gpa;
public Student(String name, int age, double gpa) {
this.name = name;
this.age = age;
this.gpa = gpa;
}
@Override
public int compareTo(Student other) {
int nameCompare = this.name.compareTo(other.name);
if (nameCompare != 0) return nameCompare;
return Integer.compare(this.age, other.age);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Student s = (Student) obj;
return age == s.age && Double.compare(gpa, s.gpa) == 0 && name.equals(s.name);
}
public String getName() { return name; }
public int getAge() { return age; }
public double getGpa() { return gpa; }
@Override
public String toString() {
return name + "(age=" + age + ", gpa=" + gpa + ")";
}
}
// Usage
import java.util.*;
public class ComparableDemo {
public static void main(String[] args) {
List<Student> students = new ArrayList<>(Arrays.asList(
new Student("Charlie", 20, 3.5),
new Student("Alice", 22, 3.8),
new Student("Bob", 21, 3.6),
new Student("Alice", 19, 3.9)
));
Collections.sort(students);
students.forEach(s -> System.out.println(s));
// Alice(19, 3.9), Alice(22, 3.8), Bob(21, 3.6), Charlie(20, 3.5)
}
}
Key points:
- compareTo() is called by Collections.sort(), TreeSet, TreeMap
- Must be consistent with equals() for correct Set/Map behavior
- Use
Integer.compare(),Double.compare()instead of subtraction (avoids overflow)
Comparator
Comparator
Comparator<T> defines custom ordering separate from the class's natural ordering. It has a single method compare(T o1, T o2).
import java.util.*;
public class ComparatorDemo {
static Comparator<String> byLength = (a, b) -> Integer.compare(a.length(), b.length());
public static void main(String[] args) {
List<String> names = new ArrayList<>(Arrays.asList(
"Charlie", "Alice", "Bob", "David"
));
names.sort(byLength);
System.out.println("By length: " + names); // [Bob, Alice, David, Charlie]
names.sort(Comparator.comparingInt(String::length)
.thenComparing(Comparator.naturalOrder()));
System.out.println("By length then alpha: " + names);
names.sort(Comparator.comparingInt(String::length).reversed());
System.out.println("By length reversed: " + names);
}
}
Key points:
- Comparator is external to the class — doesn't modify it
- Use when you need multiple sort orders
- compare(o1, o2) vs o1.compareTo(o2)
Lambda Syntax
Lambda Comparators (Java 8+)
Java 8 introduced lambda expressions and method references, making Comparator creation concise.
import java.util.*;
public class LambdaComparatorDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList("Charlie", "Alice", "Bob", "David");
names.sort((a, b) -> a.length() - b.length());
System.out.println("By length: " + names);
names.sort(Comparator.comparingInt(String::length));
System.out.println("By length (method ref): " + names);
names.sort(Comparator.comparingInt(String::length).reversed());
System.out.println("By length desc: " + names);
List<String> withNulls = Arrays.asList("b", null, "a", null, "c");
withNulls.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
System.out.println("Nulls first: " + withNulls);
withNulls.sort(Comparator.nullsLast(Comparator.naturalOrder()));
System.out.println("Nulls last: " + withNulls);
List<String> mixed = Arrays.asList("Banana", "apple", "Cherry");
mixed.sort(String.CASE_INSENSITIVE_ORDER);
System.out.println("Case insensitive: " + mixed);
}
}
Lambda patterns:
(a, b) -> a.length() - b.length()— basic lambdaComparator.comparingInt(String::length)— method reference.thenComparing()— chain conditions.reversed()— reverse orderComparator.nullsFirst/Last()— handle nulls
Chaining
Comparator Chaining
Java 8+ provides chaining methods to combine multiple sorting criteria.
import java.util.*;
public class ChainingDemo {
static class Employee {
String name;
String department;
int age;
double salary;
Employee(String name, String dept, int age, double salary) {
this.name = name; this.department = dept; this.age = age; this.salary = salary;
}
@Override
public String toString() {
return name + "(" + department + ", " + age + ", $" + salary + ")";
}
}
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>(Arrays.asList(
new Employee("Alice", "Engineering", 30, 95000),
new Employee("Bob", "Marketing", 25, 70000),
new Employee("Charlie", "Engineering", 35, 110000),
new Employee("Diana", "Marketing", 28, 80000),
new Employee("Eve", "Engineering", 25, 90000)
));
employees.sort(Comparator.comparing((Employee e) -> e.department)
.thenComparing(Comparator.comparingDouble((Employee e) -> e.salary).reversed()));
System.out.println("By dept, then salary desc:");
employees.forEach(e -> System.out.println(" " + e));
employees.sort(Comparator.comparingInt((Employee e) -> e.age)
.thenComparing((Employee e) -> e.name));
System.out.println("\nBy age, then name:");
employees.forEach(e -> System.out.println(" " + e));
Comparator<Employee> byDeptThenName =
Comparator.comparing((Employee e) -> e.department)
.thenComparing((Employee e) -> e.name);
employees.sort(byDeptThenName);
System.out.println("\nBy dept, then name:");
employees.forEach(e -> System.out.println(" " + e));
}
}
Chaining methods:
thenComparing(comparator)— add secondary sortthenComparingInt/Double/Long(keyExtractor)— primitive-specificreversed()— reverse the comparator
Best practice: Use Comparator.comparing() with method references for clean, type-safe comparators.
Practice Problems
Create a Person class implementing Comparable with natural ordering by name. Then sort a list by age descending, then by name ascending using Comparator.
Solution
import java.util.*;
public class Person implements Comparable<Person> {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person other) {
return this.name.compareTo(other.name);
}
public static void sortByAgeThenName(List<Person> people) {
people.sort(Comparator.comparingInt((Person p) -> p.age).reversed()
.thenComparing(p -> p.name));
}
@Override
public String toString() { return name + "(" + age + ")"; }
}Sort a list of strings first by length (shortest first), then alphabetically for strings of the same length.
Solution
import java.util.*;
public class StringSorter {
public static List<String> sortByLengthThenAlpha(List<String> strings) {
List<String> result = new ArrayList<>(strings);
result.sort(Comparator.comparingInt(String::length)
.thenComparing(Comparator.naturalOrder()));
return result;
}
}Write a method that takes a Map<String, Integer> and returns a new LinkedHashMap sorted by values in descending order.
Solution
import java.util.*;
public class MapSorter {
public static LinkedHashMap<String, Integer> sortByValueDesc(Map<String, Integer> map) {
return map.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.collect(LinkedHashMap::new, (m, e) -> m.put(e.getKey(), e.getValue()), LinkedHashMap::putAll);
}
}Quiz
1. What is the difference between Comparable and Comparator?
2. What method does Comparable define?
3. How do you create a max-heap Comparator for integers?
4. What does Comparator.thenComparing() do?
Flashcards
Question
What is the contract of compareTo()?
Click to reveal answer
Answer
Must be consistent with equals(), transitive, and antisymmetric. Returns negative if this < other, zero if equal, positive if this > other.
Question
How do you create a Comparator with a lambda?
Click to reveal answer
Answer
(a, b) -> Integer.compare(a.field, b.field) or Comparator.comparingInt(ClassName::getField). Use .thenComparing() to chain and .reversed() to reverse.
Question
When should you use Comparable vs Comparator?
Click to reveal answer
Answer
Comparable for the single natural ordering defined in the class. Comparator for multiple custom orderings or when you can't modify the class.
Question
How do you handle null values in Comparator?
Click to reveal answer
Answer
Use Comparator.nullsFirst(comparator) to put nulls first, or Comparator.nullsLast(comparator) to put nulls last.
Question
What is Comparable and Comparator?
Click to reveal answer
Answer
Comparable and Comparator is a key concept in Java programming.
Revision Notes
Key Takeaways
- 1.Comparable defines natural ordering inside the class
- 2.Comparator defines custom ordering externally
- 3.Use Integer.compare() not subtraction in compareTo
- 4.Chain with thenComparing() for multi-field sorting
- 5.Be consistent with equals() when implementing Comparable
Interview Tips
- •Explain the difference between Comparable and Comparator with examples
- •Demonstrate lambda comparators: Comparator.comparingInt().thenComparing()
- •Discuss why compareTo must be consistent with equals
- •Know how to sort a Map by values using streams and Comparators
Cheat Sheet
Comparable vs Comparator
Comparable
- Interface in the class
- compareTo(T o) method
- Natural ordering
- Must be consistent with equals()
Comparator
- Separate object
- compare(T o1, T o2) method
- Custom ordering
- Multiple comparators per class
Lambda Comparators
- Comparator.comparingInt(Class::field)
- .thenComparing() for chaining
- .reversed() for reverse
- nullsFirst/nullsLast for nulls