Comparator vs Comparable | Java 8

GANESH SHAH
4 min readMar 8, 2024

In Java, Comparator and Comparable are interfaces used for sorting objects, but they serve different purposes:

Comparable Interface:

The Comparable interface is implemented by a class to define its natural ordering.

It contains a single method compareTo(Object obj) which compares the object with another object.

By implementing Comparable, a class indicates that its instances can be sorted using the natural ordering.

For example, classes like String, Integer, and Date implement Comparable to define their natural ordering.

Comparator Interface:

The Comparator interface provides a way to define custom ordering for objects that may not have a natural ordering or for cases where you want to define multiple sorting criteria.

It contains two methods: compare(Object obj1, Object obj2) and equals(Object obj).

The compare() method compares two objects for ordering.

The equals() method checks whether the Comparator is equal to another Comparator.

Comparator allows for flexible sorting strategies and is often used when you want to sort objects in different ways or when sorting objects of classes that you cannot modify (e.g., sorting a list of objects of a library class).

Let’s understand with an example :

We will create a list of objects of the User class and will try to sort it using both the comparator and comparable and will try to implement the same in multiple ways :

Using Comparable :

Create a User class: Notice that the User class implements a Comparable interface. It overrides compareTo with the logic of how objects should be compared.

The compareTo method returns value as : 1,0,-1 and the two objects are swapped only when the compareTo method returns 1.

  • If compareTo() returns a negative integer, it indicates that the current object should come before the object being compared to.
  • If compareTo() returns zero, it means that the two objects are considered equal in terms of ordering.
  • If compareTo() returns a positive integer, it means that the current object should come after the object being compared to.
package comparator;

public class User implements Comparable<User> {
private String name;
private Integer age;

public User(String name, Integer age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

@Override
public int compareTo(User o) {
return this.getAge().compareTo(o.getAge());
}
}

Test Comparable implementation :

package comparator;

import java.util.Comparator;
import java.util.List;

public class ComparatorExample {

public static void main(String[] args) {

User user1 = new User("ganesh",27);
User user2 = new User("digambar",26);
User user3 = new User("sumit",47);
User user4 = new User("pramod",27);
User user5 = new User("mrityunjoy",32);

List<User> listOfUsers = List.of(user1,user2,user3,user4,user5);

// comparable
listOfUsers.stream().sorted().forEach(v -> System.out.println(v.getName()));

// if we implement comparable then only one type of sorting is allowed because compareTo method can be overridden only once
// if we use comparator we can define any number of sorting type

}


}

Executing the above code provides the output as below :

Output :  
digambar
ganesh
pramod
mrityunjoy
sumit

Using Comparator :

  1. Using static comparator function comparing() :
Comparator<User> compareByAgeUsingCompareInt = Comparator.comparing(User::getAge);

2. Using lambdas :

Comparator<User> compareByAgeUsingLambdas = (User user01, User user02) -> Integer.compare(user01.getAge(), user02.getAge());

3. Directly using lambdas inside sort() :

// Ascending order sorting based on users age
listOfUsers.stream().sorted((v1,v2) -> v1.getAge() - v2.getAge()).forEach(v -> System.out.println(v.getName()));
// Descending order sorting based on users age
listOfUsers.stream().sorted((v1,v2) -> v2.getAge() - v1.getAge()).forEach(v -> System.out.println(v.getName()));

// Sorting using lambdas along with compareTo() of compared attribute data types class. Here String already implements compareTo()
listOfUsers.stream().sorted((v1,v2) -> v1.getName().compareTo(v2.getName())).forEach(v -> System.out.println(v.getName()));
listOfUsers.stream().sorted((v1,v2) -> v2.getName().compareTo(v1.getName())).forEach(v -> System.out.println(v.getName()));

4. Implement Comparator interface with concrete comparator class :

package comparator;

import java.util.Comparator;

public class CompareUsersByAgeDescendingComparator implements Comparator<User> {
@Override
public int compare(User o1, User o2) {
return o2.getAge().compareTo(o1.getAge());
}
}

Summarized code implementation :

package comparator;

import java.util.Comparator;
import java.util.List;

public class ComparatorExample {

public static void main(String[] args) {

User user1 = new User("ganesh",27);
User user2 = new User("digambar",26);
User user3 = new User("sumit",47);
User user4 = new User("pramod",27);
User user5 = new User("mrityunjoy",32);

List<User> listOfUsers = List.of(user1,user2,user3,user4,user5);

// comparable
listOfUsers.stream().sorted().forEach(v -> System.out.println(v.getName()));

Comparator<User> compareByAgeUsingCompareInt = Comparator.comparing(User::getAge);
Comparator<User> compareByAgeUsingLambdas = (User user01, User user02) -> Integer.compare(user01.getAge(), user02.getAge());

System.out.println("Sorted using compareByAgeUsingCompareInt : ");
listOfUsers.stream().sorted(compareByAgeUsingCompareInt).forEach(v -> System.out.println(v.getName()));
System.out.println("Sorted using compareByAgeUsingLambdas : ");
listOfUsers.stream().sorted(compareByAgeUsingLambdas).forEach(v -> System.out.println(v.getName()));

System.out.println("Sorted using lambdas inside sorted() ascending age : ");
listOfUsers.stream().sorted((v1,v2) -> v1.getAge() - v2.getAge()).forEach(v -> System.out.println(v.getName()));
System.out.println("Sorted using lambdas inside sorted() descending age : ");
listOfUsers.stream().sorted((v1,v2) -> v2.getAge() - v1.getAge()).forEach(v -> System.out.println(v.getName()));

System.out.println("Sorted using lambdas inside sorted() ascending name : ");
listOfUsers.stream().sorted((v1,v2) -> v1.getName().compareTo(v2.getName())).forEach(v -> System.out.println(v.getName()));
System.out.println("Sorted using lambdas inside sorted() descending name : ");
listOfUsers.stream().sorted((v1,v2) -> v2.getName().compareTo(v1.getName())).forEach(v -> System.out.println(v.getName()));

System.out.println("Sorted using CompareUsersByAgeDescendingComparator : ");
listOfUsers.stream().sorted(new CompareUsersByAgeDescendingComparator()).forEach(v -> System.out.println(v.getName()));

// if we implement comparable then only one type of sorting is allowed because compareTo method can be overridden only once
// if we use comparator we can define any number of sorting type
}


}

Summary:

Comparable is used to define the natural ordering of objects within a class.

Comparator is used to define custom ordering for objects, providing flexibility in sorting strategies, especially when you cannot or do not want to modify the original class.

--

--

GANESH SHAH

Passionate Java developer with demonstrated expertise in creating robust Java APIs solving business use cases.