Architecture & DesignLearn The Differences Between Comparable and Comparator

Learn The Differences Between Comparable and Comparator

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Sorting is a common operation on lists/collections, such as to arrange the content into a particular order. In a list, sorting is done according to the natural ordering of the content, but this may not always be the case. Java provides two interfaces to help with sorting: Comparable and Comparator. This article delves into the concept of both interfaces to show how they differ; code examples are given to help you learn.

Sorting in Lists

The content of lists in Java are sorted according to natural order. This means that, if the content are numbers, it is sorted as ascending or lowest to highest order when sorted. Similarly, characters are sorted according to their alphabetical order. But, this may not always be the case we need. There must be some way to override the natural ordering principle on the content of the list.

Java provides two interfaces to help with the sort according to our custom requirement. These two are called Comparable and Comparator. They are declared as a public interface by the Java API so that we can use them to fit our requirements.

The Comparable and Comparator Interfaces

The Comparable interface imposes a total ordering on the implemented class. This total ordering is referred to as the natural ordering. The comparison for ordering is asserted by the compareTo method of this interface, called the natural ordering method. Collections and lists are automatically sorted by the set of overloaded static variation of the Collection.sort and Arrays.sort methods.

For example, the overloaded sort methods defined by the Collection are follows:

  • static <T extends Comparable<? Super T>> void sort(List<T> list): This method sorts the specified list into ascending order.
  • static <T> void sort(List<T> list, Comparator<? Super T> c): This method sorts the specified list according to the order caused by the Comparator.

And, the overloaded sort method defined by the Arrays are many. The two of them with Comparator are as follows:

  • static<T> void sort(T[] a, int fromIndex, int toIndex, Comparator<? Super T> c): This method sorts the specified list according to the order caused by the Comparator, but sorts only the range indicated by the fromIndex and toIndex values denoting the index position of the array.
  • static<T> void sort(T[] a, Comparator<? Super T> c): This method sorts the array according to the order caused by the Comparator.

The Comparator is annotated as a public @FunctionalInterface. It imposes total ordering on a collection of objects. In the sort method, the Comparator is passed as one of the arguments to give precise control over the sorting order. This is important because every collection many not adhere to the natural order. For example, a set of collection objects may be differently ordered or may not have its natural ordering. Natural ordering is applied mostly to numbers and characters. In such a case, the default ordering imposed by the comparator on the set of elements is inconsistent. The ordering imposed by the comparator, c, on a set of elements is said to be consistent only if c.compare(e1, e2) == 0 has the same Boolean value as e1.equals(e2) for every e1 and e2 in the set of elements.

The Difference

The Comparable interface is particularly useful for imposing natural ordering on the contents of the list, whereas the Comparator interface provides a precise control over the ordering.

Although the Collection and Arrays classes have numerous overloaded sort methods, there are a couple of them that take an array and a Comparator object.

Following is a quick code to show how sorting by natural ordering works. For numbers, the sorting is done from low to high. Similarly, for String objects, the default natural ordering is alphabetically sorting. The String class implements the Comparable interface; as a result, the sorting works as we expect. Otherwise, it would have thrown ClassCastException.

package sample;
public class Car {
   private String model
   private double mileage;
   public Car(String model, double mileage) {
      this.model = model;
      this.mileage = mileage;
   }
   public String getModel() {
      return model;
   }
   public void setModel(String model) {
      this.model = model;
   }
   public double getMileage() {
      return mileage;
   }
   public void setMileage(double mileage) {
      this.mileage = mileage;
   }
   @Override
   public String toString() {
      return "Car{" +
             "model='" + model + ''' +
             ", mileage=" + mileage +
             '}';
   }
}

package sample;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class TestApp {
   public static void main(String[] args) {
      final List<Integer> numbers =
         Arrays.asList(10,90,50,-62,88,-45,-37,20,40,25);
      Collections.sort(numbers);
      Arrays.sort(days);
      for(Integer i: numbers) {
         System.out.println(i);
      }

      final List<Car> cars = Arrays.asList(
         new Car("Maruti Swift Diesel",28.4),
         new Car("Maruti Ciaz Diesel",28.09),
         new Car("Maruti Baleno Diesel",27.39),
         new Car("Honda Jazz Diesel",27.3),
         new Car("Tata Tiago Diesel",27.28),
         new Car("Maruti Ignis Diesel",26.8),
         new Car("New Toyota Prius",26.27)
      );
      cars.sort(Comparator.comparingDouble(Car::getMileage).
         reversed());
      cars.forEach(System.out::println);
   }
}

Implementation

Now, if we want to impose our own ordering principle, we need to provide an implementation of the Comparator interface to the sort method. This is not very difficult. The interface supplies two methods, such as:

  • int compare(T o1, T o2) for the implementation type T and
  • Boolean equals(Object obj)

Note that the compare method returns a negative integer, zero, or a positive integer if

  • the first argument is sorted before the second or the first argument is less than the second.
  • two are same or equal.
  • the second argument is sorted before the first or the second argument is more than the second.

For example, if we want a reverse order of the array as shown in the preceding code, we may do it as follows:

package sample;
import java.util.Comparator;
class DescendingIntOrder implements Comparator<Integer> {
   @Override
   public int compare(Integer i1, Integer i2) {
      return i2-i1;
   }
}

package sample;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class TestApp {
   public static void main(String[] args) {
      final List<Integer> numbers =
         Arrays.asList(10,90,50,-62,88,-45,-37,20,40,25);
      Collections.sort(numbers);
      Arrays.sort(days);
      for(Integer i: numbers) {
         System.out.println(i);
      }

      final List<Car> cars = Arrays.asList(
         new Car("Maruti Swift Diesel",28.4),
         new Car("Maruti Ciaz Diesel",28.09),
         new Car("Maruti Baleno Diesel",27.39),
         new Car("Honda Jazz Diesel",27.3),
         new Car("Tata Tiago Diesel",27.28),
         new Car("Maruti Ignis Diesel",26.8),
         new Car("New Toyota Prius",26.27)
      );
      cars.sort(Comparator.comparingDouble(Car::getMileage).
         reversed());
      cars.forEach(System.out::println);
   }
}

If we want to custom create our own Comparator, we may do so as follows.

package sample;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class TestApp {
   public static void main(String[] args) {
      final List<Car> cars = Arrays.asList(
         new Car("Maruti Swift Diesel",28.4),
         new Car("Maruti Ciaz Diesel",28.09),
         new Car("Maruti Baleno Diesel",27.39),
         new Car("Honda Jazz Diesel",27.3),
         new Car("Tata Tiago Diesel",27.28),
         new Car("Maruti Ignis Diesel",26.8),
         new Car("New Toyota Prius",26.27)
      );
      cars.sort(new Comparator<Car>() {
         @Override
         public int compare(Car o1, Car o2) {
            if(o1.getMileage() == o2.getMileage()) {
               return 0;
            }
            return (o1.getMileage() - o2.getMileage())
               < 0 ? -1 : 1;
         }
      });
      cars.forEach(System.out::println);
   }
}

Of course, we also can apply a chain of comparisons as follows.

package sample;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class TestApp {
   public static void main(String[] args) {
      final List<Car> cars = Arrays.asList(
         new Car("Maruti Swift Diesel",28.4),
         new Car("Maruti Ciaz Diesel",28.09),
         new Car("Maruti Baleno Diesel",27.39),
         new Car("Honda Jazz Diesel",27.3),
         new Car("Tata Tiago Diesel",27.28),
         new Car("Maruti Ignis Diesel",26.8),
         new Car("New Toyota Prius",26.27)
      );

      cars.sort(Comparator.comparing(Car::getMileage)
         .reversed()
         .thenComparing(Comparator.comparing(Car::getModel)
         .reversed()));

      cars.forEach(System.out::println);
   }
}

Conclusion

It is perhaps evident now that both the Comparable and Comparator have very important and interesting uses. In fact, both of them are more complementary than distinct in their implementation. What distinguishes Comparable from Comparator is that the Comparable interface is particularly useful for imposing natural ordering on the contents of the list whereas the Comparator interface provides precise control over the ordering. That’s all.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories