Comparator, Comparable und compareTo – Vergleichen von Objekten in Java - Feature-Bild

Comparator, Comparable, compareTo – Comparing Java Objects

Author image
by Sven WoltmannOctober 6, 2020

This article explains:

  • How to compare two objects in Java?
  • What is a Comparator, and what do you need it for?
  • What are the possibilities for creating a Comparator?
  • How to create a Comparator with Java 8
  • What is the difference between Comparator 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.

How to Compare Two Objects in Java?

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.

How to Compare Two Strings in Java?

Suppose we have the following two strings:

String s1 = "Happy"; String s2 = "Coders";
Code language: Java (java)

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);
Code language: Java (java)

The result variable now contains:

  • a value less than 0 if s1 comes before s2 according to alphabetical sorting
  • 0, if s1 and s2 are equal (i.e. s1.equals(s2) is true)
  • a value greater than 0 if 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.)

Sorting Strings in Java

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)); } }
Code language: Java (java)

The program prints the names in alphabetical order:

[James, Jennifer, John, Mary, Patricia, Robert]
Code language: plaintext (plaintext)

(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 Java Comparable Interface

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).

Java Comparable Example

To make custom classes comparable and thus sortable, you have to implement the Comparable interface 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 Comparable<Student> { 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; } } }
Code language: Java (java)

Using the ternary operator, you can write the compareTo() method as a one-liner:

@Override public int compareTo(Student o) { return this.id < o.id ? -1 : (this.id == o.id ? 0 : 1); }
Code language: Java (java)

It is even more elegant to call the static Integer.compare() method to compare the two int values:

@Override public int compareTo(Student o) { return Integer.compare(this.id, o.id); }
Code language: Java (java)

Don't worry, the JVM inlines this method call, 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); } }
Code language: Java (java)

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'}]
Code language: plaintext (plaintext)

Let's get back to the String that we want to sort by length, not alphabetically.

The Java Comparator Interface

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:

  • a value less than 0 if o1 is less than o2
  • 0, if o1 and o2 are equal (i.e. o1.equals(o2) returns true)
  • a value greater than 0 if o1 is greater than o2

Java Comparator Example: Sorting Strings by Length

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; } } }
Code language: Java (java)

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); }
Code language: Java (java)

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)); } }
Code language: Java (java)

The names are no longer sorted alphabetically, but by their length in ascending order:

[Mary, John, James, Robert, Patricia, Jennifer]
Code language: plaintext (plaintext)

How to Create a Comparator?

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().

Comparator as a Public Class

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());
Code language: Java (java)

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();
Code language: Java (java)

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()); } }
Code language: Java (java)

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; } }
Code language: Java (java)

We would use this comparator, for example, as follows to sort our list of names in descending order:

Arrays.sort(names, StringLengthComparator.DESC);
Code language: Java (java)

A public class thus gives us the greatest possible freedom and flexibility in defining our comparator.

Comparator as an Anonymous Class

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 last name:

students.sort(new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o1.getLastName().compareTo(o2.getLastName()); } });
Code language: Java (java)

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; } });
Code language: Java (java)

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):

Refactoring a Comparator to a Lambda expression

You will find out what the result is in the next section.

Writing a Java Comparator as a Lambda

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()));
Code language: Java (java)

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; });
Code language: Java (java)

Things will get really nice with the method that I will show in the following section. A modern IDE also offers us this step:

Refactoring to a Comparator chain

Java 8: Creating a Comparator With Comparator.comparing()

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 last name, we can write the following:

students.sort(Comparator.comparing(Student::getLastName));
Code language: Java (java)

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));
Code language: Java (java)

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));
Code language: Java (java)

Comparator.comparing() and the comparator chains we can build with it make the code shorter and more concise.

Comparable vs. Comparator – Summary

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, please share it using one of the share buttons at the end or leave me a comment.

Do you want to be informed when new articles are published on HappyCoders.eu? Then click here to sign up for the HappyCoders.eu newsletter.