In this tutorial, we’ll learn about Comparable and Comparator interfaces in Java. It’ll help us to compare our custom object types.
Let’s suppose we have our Student class:
class Student { private int id; private String name; //constructor, getters, setters }
And we have a List of Student objects:
List<Student> students = new ArrayList<>(); students.add(1210, "James"); students.add(1101, "John"); students.add(1161, "Kate");
What if at a later date we want to sort our list of Student objects say by their name or ids? Since these are custom objects, Java doesn’t know a way to compare them while sorting. So our act of implementing Comparable/Comparator interfaces let the Java compiler know a means to go about comparing and sorting these custom objects.
java.lang.Comparable interface needs to be implemented by the class in context eg: our Student class. It helps us to compare the instances of that class. It’s a default ordering of a class that’s used while comparing its objects unless we specifically mention a Comparator to be used.
public class Student implements Comparable<Student> { ... @Override public int compareTo(Student otherStudent) { return (this.getId() - otherStudent.getId()); } }
Comparable is a Functional Interface. Its compareTo() method returns an int value. When sorting objects in ascending order, our method should return values in accordance with the below rules:
currentObj > otherObj , returns 1 currentObj == otherObj , returns 0 currentObj < otherObj , returns -1
With the above code implementation in place, invoking:
Collections.sort(students);
will sort our Student objects by their ids (ascending order).
What if we want to define multiple implementations of comparing objects? What if in one of the use cases we had to sort our list by Student names?
java.util.Comparator interface doesn’t need to be implemented by the class in context. So we have a choice to define multiple Comparator classes for different use cases.
Let’s start by writing our own Comparator implementation to compare students by their name:
class CompareStudentsByName implements Comparator<Student> { @Override public int compare(Student student, Student otherStudent) { return student.getName().compareTo(otherStudent.getName()); } }
Please note that theĀ compare() method takes in two arguments, the objects to be compared. Also in the above implementation, we have used compareTo() method of String class to help us with a name-based comparison.
To sort the objects by their name, we’ll use:
Collections.sort(students, new CompareStudentsByName());
In the sort() method, we must explicitly pass our Comparator instance as its second argument.
It’s pretty easy for us to define a Comparator in Java 8. We can write a lambda:
Comparator<Student> compareStudentByName = (s1, s2) -> s1.getName() .compareTo(s2.getName());
Or, we can use Comparator.comparing() method:
Comparator<Student> compareStudentsByName = Comparator.comparing(Student::getName);
Now that we have looked at the Comparable and Comparator interfaces in detail, let’s sum up the differences between them.
Comparable | Comparator |
---|---|
Belongs to java.lang package | Belongs to java.util package |
Has a single abstract method compareTo(otherObj) which takes in a single argument | The only abstract method compare(obj, otherObj) takes in two objects to be compared as the arguments |
A class in context, the instances of which are to be compared, needs to implement this interface | We need to explicitly define another implementation class |
Used to define the default or the natural ordering | Used for extraneous custom object comparison use cases |
In this tutorial, we learned how and when to use Comparable and Comparator interfaces. We also looked at the differences between the two of them.