DEV Community

Tapas Pal
Tapas Pal

Posted on • Edited on

1. Demystifying Comparable Vs Comparator

Q1. Difference between Comparable and comparator

Comparable = "How should this object normally be sorted?"
Comparator = "How do I want to sort it right now?"

public class Employee implements Comparable<Employee> {

    private int id;
    private String name;
    private double salary;

    public Employee(int id, String name, double salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }

    @Override
    public int compareTo(Employee other) {
        return Integer.compare(this.id, other.id);
    }

    @Override
    public String toString() {
        return id + " " + name;
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

101 Tom
102 David
103 John
Enter fullscreen mode Exit fullscreen mode

Because the class itself says:
compareTo() => sort by id

This becomes the natural ordering.
Why Comparable Can Become a Problem
Imagine HR wants:
Sort by salary
Finance wants:
Sort by employee id
Manager wants:
Sort by name

With Comparable you can only have ONE natural ordering.
You cannot implement:
compareToBySalary()
compareToByName()
compareToByDepartment()

Only one compareTo() exists.
That's where Comparator comes in.

Comparator - Sort by salary:

employees.sort(
    Comparator.comparing(Employee::getSalary)
);
Enter fullscreen mode Exit fullscreen mode

Sort by name:

employees.sort(
    Comparator.comparing(Employee::getName)
);
Enter fullscreen mode Exit fullscreen mode

Sort by id descending:

employees.sort(
    Comparator.comparing(Employee::getId)
              .reversed()
);
Enter fullscreen mode Exit fullscreen mode

No need to modify Employee class.

Sort by age then name

employees.sort(
        Comparator.comparing(Employee::getAge)
                  .thenComparing(Employee::getName)
);
Enter fullscreen mode Exit fullscreen mode

What Happens Internally?
Comparable:

Collections.sort(list);

Java internally calls:

obj1.compareTo(obj2);
Enter fullscreen mode Exit fullscreen mode

Comparator:

Collections.sort(list, comparator);
Enter fullscreen mode Exit fullscreen mode

Java internally calls:

comparator.compare(obj1, obj2);
Enter fullscreen mode Exit fullscreen mode

Why? if I use Comparator it does not require to implement compare() in the Employee class but if I use Comparable interface I need to override compareTo method?

Comparable = Object Knows How To Compare Itself

public class Employee
        implements Comparable<Employee> {

    @Override
    public int compareTo(Employee other) {
        return Integer.compare(this.id, other.id);
    }
}
Enter fullscreen mode Exit fullscreen mode

you are telling Java:
Every Employee knows how to compare itself with another Employee.
Then Java can do:
e1.compareTo(e2);
because the method exists inside Employee.
Comparator = Separate Comparison Object

No Comparable. No compareTo().
Now you write:

Comparator<Employee> comparator =
        Comparator.comparing(Employee::name);
Enter fullscreen mode Exit fullscreen mode

Java now has an external object:

Comparator<Employee>
whose job is: compare(e1, e2)
The comparison logic is no longer inside Employee.
Why Employee Doesn't Need to Implement Comparator
Because Comparator is not part of Employee.

Comparator<Employee> byName =
        Comparator.comparing(Employee::name);

Comparator<Employee> bySalary =
        Comparator.comparing(Employee::salary);
Enter fullscreen mode Exit fullscreen mode

Now you have two different comparison strategies.
Employee remains unchanged.
Why We Rarely Do This
Technically you can write:

public class Employee
        implements Comparator<Employee> {

    @Override
    public int compare(Employee e1,
                       Employee e2) {
        return e1.id - e2.id;
    }
}
Enter fullscreen mode Exit fullscreen mode

But this is bad design.
Now Employee is:
An employee AND A comparison engine
Two responsibilities.
Violates the Single Responsibility Principle.

If String implementation it is using "Tom".compareTo("John") then how we able to sort List? where do it iterate ?

You write:

employees.sort( Comparator.comparing(Employee::name));
Enter fullscreen mode Exit fullscreen mode

and wonder:
If Java is doing "Tom".compareTo("John"), who is iterating through the List?
The answer is:
The sorting algorithm inside List.sort() **(TimSort in modern Java) **iterates through the list and repeatedly calls the comparator.

Step 1: Your List
Suppose:

List<Employee> employees = List.of(
    new Employee(101, "Tom", 70000),
    new Employee(102, "David", 80000),
    new Employee(103, "John", 90000)
);
Enter fullscreen mode Exit fullscreen mode

Initial:
[Tom, David, John]

Step 2: Create Comparator
This: Comparator.comparing(Employee::name)
creates something similar to:

Comparator<Employee> comparator =
    (e1, e2) -> e1.name().compareTo(e2.name());
Enter fullscreen mode Exit fullscreen mode

Step 3: Java Starts Sorting
When you call: employees.sort(comparator);
Java internally does something like:
TimSort.sort(employees, comparator);
You don't see this code, but the sorting algorithm repeatedly asks:
comparator.compare(e1, e2)

Step 4: Actual Comparisons
Suppose the algorithm picks:

Employee(101, "Tom")
Employee(102, "David")
and calls:
comparator.compare(tom, david);
which executes:

tom.name().compareTo(david.name())
Enter fullscreen mode Exit fullscreen mode

becomes: "Tom".compareTo("David")
Result:
positive number
Meaning: Tom should come after David
Next comparison: comparator.compare(tom, john);
becomes: "Tom".compareTo("John")
Result: positive number
Meaning: Tom should come after John

Next comparison:
comparator.compare(david, john);
becomes: "David".compareTo("John")
Result: negative number
Meaning: David should come before John

Final order:
David
John
Tom
Important Point - You are NOT iterating.
You only provide: Comparator
Java's sorting algorithm does the iteration.

Think of it like this:
You:
Here is the rule for comparing employees.
Java:
Thanks. I'll repeatedly apply that rule
while rearranging the list.
Similar to Collections.sort()

When you write:
Collections.sort(employees, comparator);
internally Java does something similar to:

for (...) {
    if (comparator.compare(a, b) > 0) {
        swap(a, b);
    }
}
Enter fullscreen mode Exit fullscreen mode

The real algorithm is TimSort (much more sophisticated), but conceptually it's repeatedly:
compare()
compare()
compare()
compare()
swap()
compare()
...
How Many Times Is compare() Called?
For 3 employees:
Tom
David
John
you might see:

Comparator<Employee> comparator =
    (e1, e2) -> {
        System.out.println(
            "Comparing " + e1.name()
            + " and "
            + e2.name()
        );
        return e1.name().compareTo(e2.name());
    };

Enter fullscreen mode Exit fullscreen mode

Output could be:

Comparing David and Tom
Comparing John and David
Comparing John and Tom
Comparing John and David

Notice:
Java chooses which elements to compare.
The sorting algorithm controls the iteration.
Your comparator only provides the comparison logic.
Senior Developer Mental Model

When you write:

Comparator.comparing(Employee::name)
think of it as generating:

(Employee e1, Employee e2)
    -> e1.name().compareTo(e2.name())
Enter fullscreen mode Exit fullscreen mode

Then: employees.sort(...)

means:
Java sorting algorithm
+
Your comparison rule
=
Sorted list

The iteration is performed by the sorting algorithm (TimSort), not by Comparator, Comparable, or your Employee class.

VERY IMPORTANT OBSERVATION - DEEP DRIVE

my 
List<Employee> employees = new java.util.ArrayList<>(List.of(
                new Employee(103, "John", 90000),
                new Employee(101, "Tom", 70000),
                new Employee(104, "Tom", 80000),
                new Employee(102, "David", 80000)
        ));
and 
employees.sort(new Comparator<Employee>() {
            @Override
            public int compare(Employee o1, Employee o2) {
                System.out.println(
                        "Comparing " + o1.name()
                                + " and "
                                + o2.name()
                );
                return o1.name().compareTo(o2.name());
            }
        });
It prints : But why?
Comparing Tom and John
Comparing Tom and Tom
Comparing David and Tom
Comparing David and Tom
Comparing David and John 
Enter fullscreen mode Exit fullscreen mode

*This is a fantastic observation because it reveals how Java's sorting algorithm works.
*

Your list is:

[
    John,
    Tom,
    Tom,
    David
]
Enter fullscreen mode Exit fullscreen mode

and your comparator prints:

Comparing Tom and John
Comparing Tom and Tom
Comparing David and Tom
Comparing David and Tom
Comparing David and John
Enter fullscreen mode Exit fullscreen mode

You expected something like:

John vs Tom
Tom vs Tom
Tom vs David
...
Enter fullscreen mode Exit fullscreen mode

but sorting algorithms do not compare elements in a simple left-to-right order.

What's Actually Happening?
When you call: employees.sort(comparator);
Java uses TimSort (since Java 7).

TimSort is:
Stable
Optimized for partially sorted data
Not a simple bubble sort

Therefore:

  • You should never assume the order of compare() calls.
  • The sorting algorithm decides:
  • Which elements to compare
  • How many times to compare
  • Whether to compare the same elements again

Let's Analyze Your Output
Initial list:

Index 0 -> John
Index 1 -> Tom
Index 2 -> Tom
Index 3 -> David
Enter fullscreen mode Exit fullscreen mode

Comparison 1
Comparing Tom and John
Internally: "Tom".compareTo("John")
Result: positive
Meaning: Tom should come after John

Comparison 2
Comparing Tom and Tom
Second Tom compared with first Tom.
"Tom".compareTo("Tom")
Result: 0
Meaning: equal

Comparison 3
Comparing David and Tom
"David".compareTo("Tom")
Result: negative
Meaning: David comes before Tom

Comparison 4
Comparing David and Tom
Again!
Many developers find this surprising.
But sorting algorithms often compare the same elements multiple times.
There is no guarantee: compare(a,b) called only once

In fact:
compare(a,b)
compare(a,b)
compare(a,b)

can happen.
This is why comparators must be:

  • Pure
  • Stateless
  • No side effects

Comparison 5
Comparing David and John
"David".compareTo("John")
Result: negative
Meaning: David comes before John

Final Sorted List

David
John
Tom
Tom
Enter fullscreen mode Exit fullscreen mode

Why Doesn't Java Compare Every Pair?
Many people think sorting does:

John vs Tom
John vs Tom
John vs David
Tom vs Tom
Tom vs David
Tom vs David
Enter fullscreen mode Exit fullscreen mode

like a nested loop.
That would be: O(n²)
Java's TimSort is much smarter.

It attempts to discover ordered runs and merge them efficiently.
Therefore:
The compare sequence is implementation detail.
Very Important Interview Point
You should NEVER write logic like:

Comparator<Employee> comparator =
        new Comparator<>() {
            int count = 0;
            @Override
            public int compare(Employee e1,
                               Employee e2) {
                count++;
                if (count == 3) {
                    return 0;
                }
                return e1.name()
                        .compareTo(e2.name());
            }
        };
Enter fullscreen mode Exit fullscreen mode

because Java may call:
compare()
The comparator must always return the same result for the same inputs.
If You Want to See Exactly What Is Being Compared
Add more details:

@Override
public int compare(Employee o1, Employee o2) {

    int result =
            o1.name().compareTo(o2.name());

    System.out.println(
            o1.name()
            + " vs "
            + o2.name()
            + " => "
            + result
    );
    return result;
}
Enter fullscreen mode Exit fullscreen mode

Output:

Tom vs John => 10
Tom vs Tom => 0
David vs Tom => -16
David vs Tom => -16
David vs John => -6
Enter fullscreen mode Exit fullscreen mode

Now you can see why Java is moving elements around.
Senior Developer Takeaway
When using:
Collections.sort(...)
or
List.sort(...)

you should think:
I provide the comparison rule.

Java decides:
- which elements to compare
- how often to compare
- in what order to compare

The sequence of compare() calls is not deterministic API behavior and may even change between Java versions because it depends on the underlying sorting implementation (currently TimSort).
---------------------------------------------------------------

Top comments (0)