Comparable is an interface providing the default / natural ordering to the implemented objects in an array or collection (List, Set and Map)

Comparator are comparison functional interfaces passed to sort methods such as sort(Comparator) of a List instance and Arrays.sort(Comparator), and passed to SortedSet and SortedMap constructors to

  • Control over the sorting order of Comparable objects with Comparator.naturalOrder() and Comparator.reverseOrder()

  • Provide the custom sorting order of objects with Comparator.comparing(keyExtractor, keyComparator) methods

Comparator can also be used to implement the Comparable contract. This is the recommended way since Java 8+, especially when you'd like to compare multiple object fields

Both Comparable and Comparator are members of the Java Collections Framework

Lets walk through this tutorial to explore them in more details

Implement Comparable to provide the default ordering

By implementing Comparable, you can provide the default ordering to the objects in an array or collection

The following example passes Comparator.naturalOrder() to the sort method of an ArrayList instance to use the default / natural sort logic defined by the Comparable implementation of the underlying objects, ComparableBook

@Test
public void provideDefaultOrderingWithComparable() {  
    ComparableBook book1 = new ComparableBook(1, "b");
    ComparableBook book2 = new ComparableBook(2, "c");
    ComparableBook book3 = new ComparableBook(3, "a");

    List<ComparableBook> list = Arrays.asList(book1, book2, book3);
    list.sort(Comparator.naturalOrder());
    assertThat(list).containsExactly(book3, book1, book2);
}

The ComparableBook class is defined as below

class ComparableBook implements Comparable<ComparableBook> {  
    int id;
    String title;

    ComparableBook(int id, String title) {
        this.id = id;
        this.title = title;
    }

    int getId() {
        return id;
    }

    @Override
    public int compareTo(ComparableBook o) {
        return this.title.compareTo(o.title);
    }
}

Object implements Comparable need define the int compareTo(Object) contract method. String and primitive wrapper classes such as Integer, Long, Float, Double and BigInteger are already implemented compareTo method of Comparable so you can reuse them in your definition

In the above compareTo implementation example, title is used as the sort key, compareTo of String is reused to sort the keys in alphabetical / ascending order

Sort keys can also be ordered in descending by switching between the caller and the parameter of compareTo as the following implementation

@Override
public int compareTo(ComparableBook o) {  
    return o.title.compareTo(this.title);
}

If you'd like to compare multiple fields when implementing compareTo, check out the latter part of this tutorial

Use Comparator to provide the ordering of non-Comparable objects

Without the Comparable implementation, you have to specify explicitly the sorting key function by using Comparator functional interface, every time you sort the objects under an array or collection

The following example uses Comparator.comparing(keyExtractor, keyComparator) to provide the ordering. keyExtractor is a function used to extract the sort key. keyComparator is an optional Comparator, the default is Comparator.naturalOrder, used to compare the sort key

@Test
public void provideOrderingWithComparator() {  
    Book book1 = new Book(1, "b");
    Book book2 = new Book(2, "c");
    Book book3 = new Book(3, "a");

    List<Book> list = Arrays.asList(book1, book2, book3);
    list.sort(Comparator.comparing(Book::getTitle));
    assertThat(list).containsExactly(book3, book1, book2);
}

Book is defined as the following

class Book {  
    int id;
    String title;

    Book(int id, String title) {
        this.id = id;
        this.title = title;
    }

    int getId() {
        return id;
    }

    String getTitle() {
        return title;
    }
}

Use Comparator to override the default ordering of Comparable

  • You can reverse the default sorting direction of Comparable objects by using Comparator.reverseOrder()
@Test
public void reverseTheDefaultOrderingWithComparator() {  
    ComparableBook book1 = new ComparableBook(1, "b");
    ComparableBook book2 = new ComparableBook(2, "c");
    ComparableBook book3 = new ComparableBook(3, "a");

    List<ComparableBook> list = Arrays.asList(book1, book2, book3);
    list.sort(Comparator.reverseOrder());
    assertThat(list).containsExactly(book2, book1, book3);
}
  • Override the sorting key of Comparable objects with Comparator.comparing(keyExtractor, keyComparator)
@Test
public void overrideSortKeyWithComparator() {  
    ComparableBook book1 = new ComparableBook(1, "b");
    ComparableBook book2 = new ComparableBook(2, "c");
    ComparableBook book3 = new ComparableBook(3, "a");

    List<ComparableBook> list = Arrays.asList(book2, book3, book1);
    list.sort(Comparator.comparing(ComparableBook::getId));
    assertThat(list).containsExactly(book1, book2, book3);
}

Use Comparator to implement compareTo of Comparable and compare multiple fields

  • You can use Comparator.comparing to implement the compareTo contract of Comparable interface. The above definition inside ComparableBook can be rewritten like this
@Override
public int compareTo(ComparableBook o) {  
    //return this.title.compareTo(o.title);
    return Comparator
        .comparing(ComparableBook::getTitle)
        .compare(this, o);
}
  • It's especially useful when you'd like to compare multiple fields, you can attach multiple thenComparing into the chain after the first comparing
@Override
public int compareTo(ComparableBook o) {  
    //return this.title.compareTo(o.title);
    return Comparator
        .comparing(ComparableBook::getTitle)
        .thenComparing(ComparableBook::getId)
        .compare(this, o);
}
  • The multiple multiple fields comparing chain also works well when providing the inline Comparator to the sort methods of a List instance or SortedSet and SortedMap constructors
@Test
public void compareMultipleFieldsWithComparator() {  
    Book book1 = new Book(1, "b");
    Book book2 = new Book(2, "c");
    Book book3 = new Book(3, "a");

    List<Book> list = Arrays.asList(book1, book2, book3);
    list.sort(Comparator
        .comparing(Book::getTitle)
        .thenComparing(Book::getId));
    assertThat(list).containsExactly(book3, book1, book2);
}
  • You can sort the field in descending order by adding Comparator.reverseOrder() as a second argument on each comparing method
@Test
public void reverseTheComparingOfComparator() {  
    Book book1 = new Book(1, "b");
    Book book2 = new Book(2, "c");
    Book book3 = new Book(3, "c");

    List<Book> list = Arrays.asList(book1, book2, book3);
    list.sort(Comparator
            .comparing(Book::getTitle, Comparator.reverseOrder())
            .thenComparing(Book::getId, Comparator.reverseOrder()));

    assertThat(list).containsExactly(book3, book2, book1);
}
  • The reversed() method has the same above effect but it is only useful when comparing one field
@Test
public void reverseTheComparingOfComparator2() {  
    Book book1 = new Book(1, "b");
    Book book2 = new Book(2, "c");
    Book book3 = new Book(3, "a");

    List<Book> list = Arrays.asList(book1, book2, book3);
    list.sort(Comparator
            .comparing(Book::getTitle)
            .reversed());

    assertThat(list).containsExactly(book2, book1, book3);
}

Conclusion

In this tutorial, we learned the difference between Comparable and Comparator and when to use them

In short, Comparable and Comparator provide the default and custom sorting logic, respectively, to the underlying objects in an array or collection

You can find below the full source code