CodeGym/Java Blog/Java Classes/Java's Comparator class
Author
Artem Divertitto
Senior Android Developer at United Tech

Java's Comparator class

Published in the Java Classes group
members
Hi! Today we're going to talk about comparing objects. Java's Comparator class - 1 Hmm... But haven't we already talked this topic more than once? :/ We know how the == operator works, as well as the equals() and hashCode() methods. Comparison is a little different. Previously, we most likely meant "checking objects for equality". But the reasons for comparing objects with each other can be completely different! The most obvious of these is sorting. I think if you were told to sort an ArrayList<> of numbers or strings, you would be able to handle this without any problems:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       String name1 = "Masha";
       String name2 = "Sasha";
       String name3 = "Dasha";

       List<String> names = new ArrayList<>();
       names.add(name1);
       names.add(name2);
       names.add(name3);

       Collections.sort(names);
       System.out.println(names);
   }
}
Console output:

[Dasha, Masha, Sasha]
If you remembered the Collections class and its sort() method, well done! I think you'll also have no trouble with numbers. Here's a more challenging task for you:
public class Car {

   private int manufactureYear;
   private String model;
   private int maxSpeed;

   public Car(int manufactureYear, String model, int maxSpeed) {
       this.manufactureYear = manufactureYear;
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   // ...getters, setters, toString()

}

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Car> cars = new ArrayList<>();

       Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
       Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
       Car bugatti = new Car(2010, "Bugatti Veyron", 350);

       cars.add(ferrari);
       cars.add(bugatti);
       cars.add(lambo);
   }
}
The task is actually simple. We have a Car class and 3 Car objects. Would you kindly sort the cars in the list? You will probably ask, "How should they be sorted?" By name? By year of manufacture? By maximum speed? Excellent question. At the moment, we don't know how to sort the Car objects. And, quite naturally, Java doesn't know that either! When we try to pass a list of Car objects to the Collections.sort() method, we get an error:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Car> cars = new ArrayList<>();

       Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
       Car lambo = new Car(20012, "Lamborghini Gallardo", 290);
       Car bugatti = new Car(2010, "Bugatti Veyron", 350);

       cars.add(ferrari);
       cars.add(bugatti);
       cars.add(lambo);

       // Compilation error!
       Collections.sort(cars);
   }
}
And indeed, how would the language know how to sort objects of classes you've written? This depends on what your program needs to do. We must somehow teach Java to compare these objects. And to compare them just how we want. Java does have a special mechanism for this: the Comparable interface. In order to somehow compare and sort our Car objects, the class must implement this interface, which consists of a single method: compareTo():
public class Car implements Comparable<Car> {

   private int manufactureYear;
   private String model;
   private int maxSpeed;

   public Car(int manufactureYear, String model, int maxSpeed) {
       this.manufactureYear = manufactureYear;
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   @Override
   public int compareTo(Car o) {
       return 0;
   }

   // ...getters, setters, toString()

}
Please note that we specified the Comparable<Car> interface, not just Comparable. This is a parameterized interface, that is, we must specify the specific associated class. In principle, you can remove <Car> from the interface, but then the comparing will be based on Object objects by default. Instead of the compareTo(Car o) method, our class will have:
@Override
   public int compareTo(Object o) {
       return 0;
   }
Of course, it's much easier for us to work with Car. Inside the compareTo() method, we implement our logic for comparing cars. Suppose we need to sort them by year of manufacture. You probably noticed that the compareTo() method returns an int, not a boolean. Don't let this surprise you. When we compare two objects, there are 3 possibilities:
  • а < b
  • a > b
  • a == b.
boolean has only 2 values: true and false, which doesn't work well for comparing objects. With int, everything is much simpler. If the return value is > 0, then a > b. If the result of compareTo is < 0, then a < b. And, if the result is == 0, then two objects are equal: a == b. Teaching our class to sort cars by year of manufacture is easy peasy:
@Override
public int compareTo(Car o) {
   return this.getManufactureYear() - o.getManufactureYear();
}
But what's going on here? We take one Car object (this), get this car's year of manufacture, and subtract from it the year of manufacture of another car (the one with which the object is being compared). If the first car's year of manufacture is greater, the method will return an int > 0. This means that the this car > the o car. Conversely, if the year of manufacture of the second car (о) is greater, then the method will return a negative number, which means that o > this. Finally, if they are equal, then the method will return 0. This simple mechanism is already enough for us to sort collections of Car objects! You don't have to do anything else. Check it out:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Car> cars = new ArrayList<>();

       Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
       Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
       Car bugatti = new Car(2010, "Bugatti Veyron", 350);

       cars.add(ferrari);
       cars.add(bugatti);
       cars.add(lambo);

       // There was previously an error here
       Collections.sort(cars);
       System.out.println(cars);
   }
}
Console output:

[Car{manufactureYear=1990, model='Ferrari 360 Spider', maxSpeed=310}, 
Car{manufactureYear=2010, model='Bugatti Veyron', maxSpeed=350}, 
Car{manufactureYear=2012, model='Lamborghini Gallardo', maxSpeed=290}]
The cars are sorted like we want! :) Java's Comparator class - 2When should I use Comparable? The comparison method implemented in Comparable is called natural ordering. This is because in the compareTo() method you define the most common, or natural, way of comparing objects of this class. Java already has a natural ordering. For example, Java knows that strings are most often sorted alphabetically, and numbers by increasing numeric value. Therefore, if you call the sort() method on a list of numbers or strings, they will be sorted. If our program will usually compare and sort cars by year of manufacture, then we should define the natural sorting for Cars using the Comparable<Car> interface and the compareTo() method. But what if this is not enough for us? Let's imagine that our program is not so simple. In most cases, the natural sorting of cars (which we have set to be performed by year of manufacture) suits us. But sometimes our customers are aficionados of fast driving. If we are preparing a car catalog for them to peruse, the cars should be sorted by maximum speed. Java's Comparator class - 3For example, suppose we need to sort like this 15% of the time. This is clearly not enough for us to set the Car class's natural sorting to be by speed instead of by year of manufacture. But we can't ignore 15% of our customers. So what do we do? Another interface comes to our aid here: Comparator. Just like Comparable, it is a parameterized interface. What's the difference? Comparable makes our objects "comparable" and defines their most natural sort order, i.e. the sort order that will be used in most cases. Comparator is a separate "comparing" interface. If we need to implement some kind of special sorting order, we don't need to go into the Car class and change the logic of compareTo(). Instead, we can create a separate class that implements Comparator and teach it how to perform the sorting we need!
import java.util.Comparator;

public class MaxSpeedCarComparator implements Comparator<Car> {

   @Override
   public int compare(Car o1, Car o2) {
       return o1.getMaxSpeed() - o2.getMaxSpeed();
   }
}
As you can see, our Comparator is pretty simple. We need to implement only one interface method: compare(). It takes two Car objects as inputs and compares their maximum speeds in the usual way (by subtraction). Like compareTo(), it returns an int, and the principle of comparison is the same. How do we use this? It's all straightforward:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Car> cars = new ArrayList<>();

       Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
       Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
       Car bugatti = new Car(2010, "Bugatti Veyron", 350);

       cars.add(ferrari);
       cars.add(bugatti);
       cars.add(lambo);

       Comparator speedComparator = new MaxSpeedCarComparator();
       Collections.sort(cars, speedComparator);

       System.out.println(cars);
   }
}
Console output:

[Car{manufactureYear=2012, model='Lamborghini Gallardo', maxSpeed=290}, 
Car{manufactureYear=1990, model='Ferrari 360 Spider', maxSpeed=310}, 
Car{manufactureYear=2010, model='Bugatti Veyron', maxSpeed=350}]
We simply create a comparator object and pass it to the Collections.sort() method along with the list to be sorted. When the sort() method receives a comparator, it does not use the natural sorting defined in the Car class's compareTo() method. Instead, it applies the sorting algorithm defined by the comparator passed to it. What are the advantages of doing this? First, compatibility with existing code. We created a new, special sorting method, while retaining the existing one that will be used most of the time. We didn't touch the Car class at all. It was a Comparable, and so it remains:
public class Car implements Comparable<Car> {

   private int manufactureYear;
   private String model;
   private int maxSpeed;

   public Car(int manufactureYear, String model, int maxSpeed) {
       this.manufactureYear = manufactureYear;
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   @Override
   public int compareTo(Car o) {
       return this.getManufactureYear() - o.getManufactureYear();
   }

   // ...getters, setters, toString()

}
Second, flexibility. We can add as many sorting algorithms as we like. For example, we can sort cars by color, speed, weight, or by how many times a car has been used in Batman movies. All we need to do is create an additional Comparator. That's it! Today you have studied two very important mechanisms that you will often use in real projects at work. But, as you know, theory without practice is nothing. Now it's time to consolidate your knowledge and complete some tasks!
Comments (7)
  • Popular
  • New
  • Old
You must be signed in to leave a comment
ジョーンズJ
Level 47 , United States
20 January 2022, 20:15
私はこれらの車が好きです
Henrique
Level 41 , São Paulo, Brazil
7 August 2020, 14:24
I didn't understand why do we need a Comparator to compare the max speed. Why we cannot use something like this?
@Override
public int compareTo(Car o) {
    return this.getMaxSpeed() - o.getMaxSpeed();
}
Because the sorting would be done by increasing max speed, just like the sorting by increasing manufacture year. Or not?
Dave Andrea
Level 41 , Canada
17 September 2020, 12:42
You may have since figured this out, but I believe the point is not that you can't do it the way you described, because you certainly can, but that by using a Comparator you don't have to tinker with the Car class. You can control the sorting without editing the class.
Justin Smith
Level 41 , Greenfield, USA, United States
20 February 2022, 14:46
I think this part would have been clearer if they had left the original sorting process in the code with Comparator. The idea is that you preserve the existing "default" compareTo method for the Car object but also provide a way to sort differently for those who need it. So for example:
Comparator speedComparator = new MaxSpeedCarComparator();
Collections.sort(cars)
// ... do work with cars sorted by year (the default)
Collections.sort(cars, speedComparator)
// ...do work with cars sorted by speed
Jay
Level 8 , United States
22 June 2022, 15:34
we need to create a new class to implenent the comparator to performing the new sorting we need, because If we need to implement some kind of special sorting order, we don't need to go into the Car class and change the logic of compareTo().
KIN SOCHEAT
Level 34 , Phnom Penh, Cambogia
29 June 2020, 03:32
implements Comparator<Car> { @Override public int compare(Car o1, Car o2) { ---------------------- implements Comparable<Car> @Override public int compareTo(Car o) { Thanks professor
qwpo
Level 33
2 April 2020, 02:57
Invoke method need to be replaced with compare. "We need to implement only one interface method: invoke()" -》compare()