This article explains:
Comparator
, and what do you need it for?Comparator
?Comparator
with Java 8Comparator
and Comparable
?But first, the question: Why do you want to compare a Java object with another?
The most significant application for comparing two objects is certainly the sorting of object lists or arrays. To sort objects, the program has to compare them and find out if one object is smaller, larger, or equal to another.
You can find the article's source code in this GitHub repository.
You compare Java primitives (int
, long
, double
, etc.) using the operators <
, <=
, ==
, =>
, >
.
That does not work for objects.
For objects, you either use a compare
or a compareTo
method instead. I will explain both methods using Strings and a self-written "Student" class as examples.
Suppose we have the following two strings:
String s1 = "Happy";
String s2 = "Coders";
Now we want to determine if s1
is less than, greater than, or equal to s2
. In other words: whether – according to alphabetical sorting* – s1
would come before or after s2
.
We do that as follows:
int result = s1.compareTo(s2);
The result
variable now contains:
s1
comes before s2
according to alphabetical sorting;s1
and s2
are equal (i.e. s1.equals(s2)
is true
);s1
comes after s2
according to alphabetical sorting.In the example above, result
would be greater than 0 because "Happy" would be sorted after "Coders".
(* In the case of String.compareTo()
, alphabetical means: according to the Unicode values of the String's characters, e.g., an uppercase letter always comes before a lowercase letter, and German umlauts come only after all regular upper and lowercase letters.)
The compareTo()
method is used behind the scenes to sort an array or list of objects, for example as follows:
public class NameSortExample {
public static void main(String[] args) {
String[] names = {"Mary", "James", "Patricia", "John", "Jennifer", "Robert"};
Arrays.sort(names);
System.out.println(Arrays.toString(names));
}
}
The program prints the names in alphabetical order:
[James, Jennifer, John, Mary, Patricia, Robert]
(The tutorial "Sorting in Java" will show you which other possibilities exist to sort objects or primitives like int
, long
, double
.)
But what if we don't want to sort strings alphabetically at all, but by their length, for example? For this, we need a so-called Comparator
. Before we get to that, let me first explain the Comparable
interface, which we just used.
The String.compareTo()
method we used above is derived from the java.lang.Comparable
interface, which is implemented by the String
class.
The Comparable
interface defines only this single method. All classes, whose objects should be comparable, implement it. Besides String
, these are, for example, Integer
, BigInteger
, Long
, Date
, LocalDateTime
, and many more.
The order resulting from the compareTo()
method is called "natural order": Strings are sorted alphabetically; dates and times are sorted in chronological order.
The following section will show you how to make a custom class comparable (and thus sortable).
To make custom classes comparable and thus sortable, you have to implement the interface Comparable
and its compareTo()
method.
The following example shows how to do this for a Student
class that should be sorted by matriculation number (ID) by default:
public class Student implements Student<Customer> {
private int id;
private String firstName;
private String lastName;
// ... constructor ...
// ... getters and setters ...
// ... toString() method ...
@Override
public int compareTo(Student o) {
if (this.id < o.id) {
return -1;
} else if (this.id == o.id) {
return 0;
} else {
return 1;
}
}
}
Using the ternary operator, you can write the compareTo()
method as a one-liner:
@Override
public int compareTo(Customer o) {
return this.id < o.id ? -1 : (this.id == o.id ? 0 : 1);
}
It is even more elegant to call the static Integer.compare()
method to compare the two int
values:
@Override
public int compareTo(Customer o) {
return Integer.compare(this.id, o.id);
}
Don't worry, the JVM optimizes this method call away, so there won't be a performance loss.
Attention – Trap: Subtracting ints
Sometimes you can see the following code in a compareTo()
method:return this.id - o.id;
You should never write it this way, because it will not work if, for example, this.id
is -2,000,000,000 and o.id
is 1,000,000,000. In this case, this.id
is smaller, so the compareTo()
method should return a negative number. But it does not because the subtraction causes an arithmetic underflow – the result of the subtraction is 1,294,967,296 – a positive number!
And here is an example that creates three students, writes them to a list, and then sorts them:
public class StudentSortExample {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student(47271, "Kerrie", "Adkins"));
students.add(new Student(99319, "Aarron", "Wicks"));
students.add(new Student(11056, "Kaya", "Molina"));
Collections.sort(students);
System.out.println("students = " + students);
}
}
As expected, the students are sorted by their matriculation number (I inserted the line breaks manually for clarity):
students = [Student{id=11056, firstName='Kaya', lastName='Molina'},
Student{id=47271, firstName='Kerrie', lastName='Adkins'},
Student{id=99319, firstName='Aarron', lastName='Wicks'}]
Let's get back to the String
that we want to sort by length, not alphabetically.
To sort two objects by an order other than their natural order (or to sort objects of classes that do not implement Comparable
at all), we have to use the java.util.Comparator
interface.
The interface defines the method compare(T o1, T o2)
to compare the two passed objects. The method has the following return values – analogous to the compareTo()
method:
o1
is less than o2
;o1
and o2
are equal (i.e. o1.equals(o2)
returns true
);o1
is greater than o2
.A comparator, which compares strings by their length, would be implemented as follows:
public class StringLengthComparator implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
if (o1.length() < o2.length()) {
return -1;
} else if (o1.length() == o2.length()) {
return 0;
} else {
return 1;
}
}
}
Again we can compress the code to a single line using the ternary operator:
@Override
public int compare(String o1, String o2) {
return o1.length() < o2.length() ? -1 : (o1.length() == o2.length() ? 0 : 1);
}
We can use the StringLengthComparator
as follows:
public class NameSortByLengthExample {
public static void main(String[] args) {
String[] names = {"Mary", "James", "Patricia", "John", "Jennifer", "Robert"};
Arrays.sort(names, new StringLengthComparator());
System.out.println(Arrays.toString(names));
}
}
The names are no longer sorted alphabetically, but by their length in ascending order:
[Mary, John, James, Robert, Patricia, Jennifer]
Up to Java 7, you could create a comparator – as shown in the example above – only by implementing the Comparator
interface.
Since Java 8, you can also notate a comparator as a Lambda expression or – quite conveniently, as you will see in a moment – using the methods Comparator.comparing()
, thenComparing()
, and reversed()
.
Using the example StringLengthComparator
, we have already seen the first variant: We write a public class and pass an instance of it to the sorting method:
Arrays.sort(names, new StringLengthComparator());
If we want to sort by string length in several places, we can also extract a constant:
private static final StringLengthComparator STRING_LENGTH_COMPARATOR =
new StringLengthComparator();
Alternatively, we could define a singleton:
public class StringLengthComparator implements Comparator<String> {
public static final StringLengthComparator INSTANCE = new StringLengthComparator();
private StringLengthComparator() {}
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(), o2.length());
}
}
A public class also gives us the possibility to control the sorting behavior by constructor parameters. For example, we could make the sort order configurable:
public class StringLengthComparator implements Comparator<String> {
public static final StringLengthComparator ASC = new StringLengthComparator(true);
public static final StringLengthComparator DESC = new StringLengthComparator(false);
private final boolean ascending;
private StringLengthComparator(boolean ascending) {
this.ascending = ascending;
}
@Override
public int compare(String o1, String o2) {
int result = Integer.compare(o1.length(), o2.length());
return ascending ? result : -result;
}
}
We would use this comparator, for example, as follows to sort our list of names in descending order:
Arrays.sort(names, StringLengthComparator.DESC);
A public class thus gives us the greatest possible freedom and flexibility in defining our comparator.
If we need a comparator in only one place, we can also define it as an anonymous class.
With the following code, for example, we sort our students by their last name:
students.sort(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getLastName().compareTo(o2.getLastName());
}
});
Since some last names occur more frequently, we should perhaps sort better by last and first name. We do this by first checking if the last names are the same. If this is the case, we also compare the first names:
students.sort(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int result = o1.getLastName().compareTo(o2.getLastName());
if (result == 0) {
result = o1.getFirstName().compareTo(o2.getFirstName());
}
return result;
}
});
In both cases, a modern IDE like IntelliJ will tell us that you can do this more elegantly from Java 8 on (and it will ideally also offer us to refactor the code):
You will find out what the result is in the next section.
From Java 8 on, we can use a Lambda instead of the anonymous class. Sorting by last name is then done as follows:
students.sort((o1, o2) -> o1.getLastName().compareTo(o2.getLastName()));
Sorting by last and first name is also made shorter by the Lambda notation:
students.sort((o1, o2) -> {
int result = o1.getLastName().compareTo(o2.getLastName());
if (result == 0) {
result = o1.getFirstName().compareTo(o2.getFirstName());
}
return result;
});
Things will get really nice with the method that I will show in the following section. A modern IDE also offers us this step:
The most elegant method for constructing a comparator, which is also available since Java 8, is the use of Comparator.comparing()
, Comparator.thenComparing()
and Comparator.reversed()
(as well as their variations for the primitive data types int
, long
and double
).
To sort the students by their last name, we can write the following:
students.sort(Comparator.comparing(Student::getLastName));
We simply pass a reference to the method that returns the field to be sorted by.
We sort by last and first names as follows:
students.sort(Comparator.comparing(Student::getLastName)
.thenComparing(Student::getFirstName));
This notation makes it very easy for us to also cover the scenario where two students have the same last name and the same first name. To sort them additionally by ID, we just have to add a thenComparingInt()
:
students.sort(Comparator.comparing(Student::getLastName)
.thenComparing(Student::getFirstName)
.thenComparingInt(Student::getId));
Comparator.comparing()
and the comparator chains we can build with it make the code shorter and more concise.
In the course of the article, we learned about the interfaces java.lang.Comparable
and java.util.Comparator
. Let me summarize the differences in a few sentences:
By implementing the Comparable.compareTo()
method, we define the natural order of objects of a class, i.e., the order by which the objects of the class are sorted by default, e.g., by Arrays.sort(arrayOfObjects)
or Collections.sort(listOfObjects)
.
Using a Comparator
, we can sort objects in an order other than their natural order. And we can sort objects that do not implement the Comparable
interface, i.e., that do not have a natural order. Since Java 8, comparators can be defined very elegantly, as for example in students.sort(Comparator.comparing(Student::getLastName)
.
If you liked the article and would like to be informed when I publish more articles, please subscribe to my newsletter using the following form. Or share the article by using one of the share buttons below.
Thank you very much, Your shared a wonderful post,This blog gives the knowledge
about java objects for beginners.i have learned alot and many basic and importants things from here.
Hello Sanjay, thanks so much for your message. I'm happy that you liked the article.