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 and constructors such as sort(Comparator) of a List instance, Arrays.sort(Comparator), SortedSet and SortedMap constructors to
Control over the sorting order of
Comparableobjects withComparator.naturalOrder()andComparator.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
Let's 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
Comparableobjects by usingComparator.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
Comparableobjects withComparator.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.comparingto implement thecompareTocontract ofComparableinterface. The above definition insideComparableBookcan 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
thenComparinginto the chain after the firstcomparing
@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
Comparatorto thesortmethods of a List instance orSortedSetandSortedMapconstructors
@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 eachcomparingmethod
@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