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
Comparable
objects 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
Comparable
objects 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
Comparable
objects 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.comparing
to implement thecompareTo
contract ofComparable
interface. The above definition insideComparableBook
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 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
Comparator
to thesort
methods of a List instance orSortedSet
andSortedMap
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 eachcomparing
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