There are 2 interfaces for sorting in Java: Comparator and Comparable.
I’ll explain to you how to define Comparator and Comparable objects, what’s the difference and when you can use it.
Let’s comparable vs comparator fight begin…
When to Use Comparable and Comparator
So what is the main difference between Comparator and Comparable interfaces?
Comparator vs Comparable what to use?
Just follow rules:
Use Comparable when:
If the class is under control and you can change its code.
If the comparable behavior should be default sorting behavior.
Use Comparator when:
You can’t change a code of the class
You need different sorting behaviors or you need to override default ordering.
Your sorting behavior should rely on external input parameters.
Natural Ordering in Java
For collections of Strings, it is natural to use alphabetical order.
For the collection of integer numbers the natural order is by numeric value—1 before 2, and so on…
Example:
package com.explainjava;
import java.util.*;
public class ComparableDemo {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("Beta");
stringList.add("Gamma");
stringList.add("Alpha");
System.out.println("unsorted string list: " + stringList);
Collections.sort(stringList);
System.out.println("sorted string list: " + stringList);
}
}
Output:
unsorted string list: [Beta, Gamma, Alpha]
sorted string list: [Alpha, Beta, Gamma]
This table contains all standard Java classes that implement Comparable interface and support natural ordering.
Class | Natural Ordering |
Byte | Signed numerical |
Character | Unsigned numerical |
Long | Signed numerical |
Integer | Signed numerical |
Short | Signed numerical |
Double | Signed numerical |
Float | Signed numerical |
BigInteger | Signed numerical |
BigDecimal | Signed numerical |
Boolean | Boolean.FALSE < Boolean.TRUE |
File | System-dependent lexicographic on pathname |
String | Lexicographic |
Date | Chronological |
CollationKey | Locale-specific lexicographic |
Enums support natural alphabetical ordering as well.
When you want to sort a collection of objects, array or use sorted collections, such as TreeSet, you have to define a sort order.
To define it you should use Comparable or Comparator interface.
Comparable interface
Let’s sort a list of Car objects.
The easiest way is defining sort rules in Car class itself.
We can extend Car class by implementing the Comparable interface.
The comparable interface defines one method:
public int compareTo(T o);
It returns a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
Let’s sort our Car by its VIN field in alphabetical order:
package com.explainjava;
import java.util.*;
public class ComparableDemo {
public static void main(String[] args) {
List<Car> carList = new ArrayList<>();
carList.add(new Car("xxxxx1", "red", 17L));
carList.add(new Car("xxxxx3", "blue", 0L));
carList.add(new Car("xxxxx2", "black", 7L));
System.out.println("unsorted car list: " + carList);
Collections.sort(carList);
System.out.println("sorted car list: " + carList);
}
static class Car implements Comparable {
String vin;
String color;
long fuel;
Car(String vin, String color, long fuel) {
this.vin = vin;
this.color = color;
this.fuel = fuel;
}
@Override
public int compareTo(Car car) {
return this.vin.compareTo(car.vin);
}
@Override
public String toString() {
return "{VIN " + this.vin + " , fuel left " + this.fuel + "}";
}
}
}
* for meaningful output we override Car’s toString() method.
Output:
unsorted car list: [{VIN xxxxx1 , fuel left 17}, {VIN xxxxx3 , fuel left 0}, {VIN xxxxx2 , fuel left 7}]
sorted car list: [{VIN xxxxx1 , fuel left 17}, {VIN xxxxx2 , fuel left 7}, {VIN xxxxx3 , fuel left 0}]
Look at the line (33).
We use compareTo() method from String class, comparing strings in alphabetical (lexicographic) order.
But what if we want to sort our Car by fuel left?
We can implement compareTo() in this way:
@Override
public int compareTo(Car car) {
if (this.fuel == car.fuel) return 0;
return this.fuel < car.fuel ? -1 : 1;
}
This method returns -1 if the compared car has more fuel, 1 if less and 0 if they are equal.
Output:
unsorted car list: [{VIN xxxxx1 , fuel left 17}, {VIN xxxxx3 , fuel left 0}, {VIN xxxxx2 , fuel left 7}]
sorted car list: [{VIN xxxxx3 , fuel left 0}, {VIN xxxxx2 , fuel left 7}, {VIN xxxxx1 , fuel left 17}]
Actually, compareTo() could implement as sophisticated logic as we can do.
The only restriction is – it should satisfy the contract:
It returns a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.
For all x and y:
- if x.compareTo(y) > 0 then y.compareTo(x) < 0, and if x.compareTo(y) < 0 then y.compareTo(x) > 0
- if x.compareTo(y) > 0 and y.compareTo(z) >0 then x.compareTo(z) > 0
- if x.compareTo(y) == 0 then for any z sign of x.compareTo(z) == sign of y.compareTo(z)
- if x.compareTo(y) throws exception y.compareTo(x) throws exception too
It throws NullPointerException if the specified object is null.
It throws ClassCastException if the specified object’s type prevents it from being compared to this object.
Reference:
Comparator interface
But what if we want to sort our Car by VIN first, and by fuel next?
For this case, Java provides mechanism how to define sorting behavior explicitly by providing an instance of the class, that implements interface Comparator.
It holds rules how to compare objects.
The interface looks like this:
int compare(T o1, T o2);
It returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second.
Let’s create two new classes, that implement Comparator interface:
static class ComparatorCarsByVin implements Comparator {
@Override
public int compare(Car car1, Car car2) {
return car1.vin.compareTo(car2.vin);
}
}
and
static class ComparatorCarsByFuel implements Comparator {
@Override
public int compare(Car car1, Car car2) {
if (car1.fuel == car2.fuel) return 0;
return car1.fuel < car2.fuel ? -1 : 1;
}
}
let’s check all example’s code:
package com.explainjava;
import java.util.*;
public class ComparableDemo {
public static void main(String[] args) {
List carList = new ArrayList<>();
carList.add(new Car("xxxxx1", "red", 17L));
carList.add(new Car("xxxxx3", "blue", 0L));
carList.add(new Car("xxxxx2", "black", 7L));
System.out.println("unsorted car list: " + carList);
Collections.sort(carList, new ComparatorCarsByVin());
System.out.println("sorted by VIN car list: " + carList);
Collections.sort(carList, new ComparatorCarsByFuel());
System.out.println("sorted by fuel left car list: " + carList);
}
static class ComparatorCarsByVin implements Comparator {
@Override
public int compare(Car car1, Car car2) {
return car1.vin.compareTo(car2.vin);
}
}
static class ComparatorCarsByFuel implements Comparator {
@Override
public int compare(Car car1, Car car2) {
if (car1.fuel == car2.fuel) return 0;
return car1.fuel < car2.fuel ? -1 : 1;
}
}
static class Car implements Comparable {
String vin;
String color;
long fuel;
Car(String vin, String color, long fuel) {
this.vin = vin;
this.color = color;
this.fuel = fuel;
}
@Override
public int compareTo(Car car) {
if (this.fuel == car.fuel) return 0;
return this.fuel < car.fuel ? -1 : 1;
}
@Override
public String toString() {
return "{VIN " + this.vin + " , fuel left " + this.fuel + "}";
}
}
}
Output:
unsorted car list: [{VIN xxxxx1 , fuel left 17}, {VIN xxxxx3 , fuel left 0}, {VIN xxxxx2 , fuel left 7}]
sorted by VIN car list: [{VIN xxxxx1 , fuel left 17}, {VIN xxxxx2 , fuel left 7}, {VIN xxxxx3 , fuel left 0}]
sorted by fuel left car list: [{VIN xxxxx3 , fuel left 0}, {VIN xxxxx2 , fuel left 7}, {VIN xxxxx1 , fuel left 17}]
We sorted our list of Cars object in two different ways: by VIN and by fuel.
Comparator.compare() has the same contract as Comparable.compareTo() method:
- if compare(x,y) > 0 then compare(y,x) < 0 and if compare(x,y) < 0 then compare(y,x) > 0
- if compare(x,y) > 0 and compare(y,z) then compare(x,z) > 0
- if compare(x,y) == 0 then for any z sign of x.compare(x,z) == sign of compare(y,z)
- if compare(x,y) throws exception compare(y,x) throws exception too
In general case there is not required that if compare(x,y)==0 then x.equals(y), but usually it is.
It throws NullPointerException if an argument is null and this comparator does not permit null arguments.
It throws ClassCastException if the arguments’ types prevent them from being compared by this comparator.
As you can see, there is a difference with Comparable.compareTo(): method compare() permits null arguments.
Reference:
Java 8 Sorting Features
Java 1.8 provides a couple of additional useful features.
First, we got a reverse version of the comparator:
List carList = new ArrayList();
carList.add(new Car("xxxxx1", "red", 17L));
carList.add(new Car("xxxxx3", "blue", 0L));
carList.add(new Car("xxxxx2", "black", 7L));
System.out.println("unsorted car list: " + carList);
Collections.sort(carList, new ComparatorCarsByVin());
System.out.println("sorted by VIN car list: " + carList);
Collections.sort(carList, new ComparatorCarsByVin().reversed());
System.out.println("sorted by VIN car list in reverse: " + carList);
Output:
unsorted car list: [{VIN xxxxx1 , fuel left 17}, {VIN xxxxx3 , fuel left 0}, {VIN xxxxx2 , fuel left 7}]
sorted by VIN car list: [{VIN xxxxx1 , fuel left 17}, {VIN xxxxx2 , fuel left 7}, {VIN xxxxx3 , fuel left 0}]
sorted by VIN car list in reverse: [{VIN xxxxx3 , fuel left 0}, {VIN xxxxx2 , fuel left 7}, {VIN xxxxx1 , fuel left 17}]
Another cool feature – we can chain comparators.
Suppose, we want to sort our cars by fuel, but for cars, that have an equal amount of fuel (it is an integer, so it is quite possible) we want black color car first.
For this case we can implement a small and easy Comparator:
static class ComparatorCarsByColor implements Comparator {
@Override
public int compare(Car car1, Car car2) {
if (car1.color.equals(car2.color)) return 0;
if (car1.color.equals("black")) return 1;
return -1;
}
}
and just add it to ComparatorCarsByFuel.
So we don’t need re-implement ComparatorCarsByFuel, but we just chain it with a new one:
List carList = new ArrayList();
carList.add(new Car("xxxxx1", "red", 17L));
carList.add(new Car("xxxxx3", "blue", 0L));
carList.add(new Car("xxxxx2", "black", 7L));
carList.add(new Car("xxxxx9", "blue", 7L));
System.out.println("unsorted car list: " + carList);
Collections.sort(carList, new ComparatorCarsByFuel());
System.out.println("sorted by fuel car list: " + carList);
Collections.sort(carList, new ComparatorCarsByFuel().thenComparing(new ComparatorCarsByColor()));
System.out.println("sorted by fuel and color car list: " + carList);
we have to change toString() method for Car object.
It indicates color now:
@Override
public String toString() {
return "{Color " + this.color + " , fuel left " + this.fuel + "}";
}
Output:
unsorted car list: [{Color red , fuel left 17}, {Color blue , fuel left 0}, {Color black , fuel left 7}, {Color blue , fuel left 7}]
sorted by fuel car list: [{Color blue , fuel left 0}, {Color black , fuel left 7}, {Color blue , fuel left 7}, {Color red , fuel left 17}]
sorted by fuel and color car list: [{Color blue , fuel left 0}, {Color blue , fuel left 7}, {Color black , fuel left 7}, {Color red , fuel left 17}]
If you start to deep into Java 8 features, it is difficult to stop.
Conclusion
So, The Collection Framework provides a full control on objects sorting behavior.
We could define ‘natural’ order for our custom objects by implementing the Comparable interface.
It’s a good solution for a case when we extend objects and its behavior is changed, so we could change the order they sorted without any changes in default sorting behavior.
Another important thing is encapsulation – the behavior of object belongs to object.
In case, when we need to sort objects in any custom way, or we are using final classes, and have no chance to override default sort behavior of object we could use numerous instances of classes that implement Comparator interface.