In Java there are two ways to impose ordering on objects. One is through Comparator
interface, and the other is through Comparable
interface. Let us see the similarities and differences between them and when should we use them.
If we are defining a class, and we want a natural ordering of a collection of the objects of the class when sorted, then we implement Comparable
interface. For example if we are defining a Book class as part of a library project, and we want to sort the collection of books by the title, then we can define the class like the following.
import java.util.ArrayList; import java.util.Collections; import java.util.List; public class Comparators { public static void main(String []args) { List<Book> books = new ArrayList<>(); books.add(new Book("The C Programming Language", "Dennis Ritche", 1978)); books.add(new Book("The Art of Computer Programming", "Donald Knuth", 1962)); books.add(new Book("Introduction to Algorithms", "CLR", 1990)); Collections.sort(books); books.forEach(System.out::println); //Java 8 way of printing } } class Book implements Comparable<Book>{ private String title; private String author; private int releaseYear; public Book(String title, String author, int releaseYear) { this.title = title; this.author = author; this.releaseYear = releaseYear; } @Override public int compareTo(Book o) { return this.title.compareTo(o.title); //Utilize String compareTo method } public String toString() { return "[" + title + ", " + author + ", " + releaseYear + "]"; } }
Suppose at some later point we also wanted sort the books based on the year published. We can create a BookYearComparator
class which implements the Comparator
interface and sort them in that like the following.
class BookYearComparator implements Comparator<Book> { @Override public int compare(Book o1, Book o2) { return o1.getReleaseYear()-o2.getReleaseYear(); } }
Collections.sort(books, new BookYearComparator()); books.forEach(System.out::println);
With Lambda expressions from Java 8, custom comparators are simple to code. The BookYearComparator
can be defined as lambda expression like the following. No need of creating a separate class just for defining the ordering. This way the lambdas have more expressive power than just classes and interfaces.
Collections.sort(books, Comparator.comparingInt(Book::getReleaseYear));
Similarly for sorting the books by author, you can use the following expression.
Collections.sort(books, Comparator.comparing(Book::getAuthor));
Alternate syntax for specifying comparator as Lambda expression is as follows.
Collections.sort(books, (b1, b2)->b1.getReleaseYear()-b2.getReleaseYear()); books.forEach(System.out::println); Collections.sort(books, (b1, b2)->b1.getAuthor().compareTo(b2.getAuthor())); books.forEach(System.out::println);
The above syntax is useful when we have to use more than one field in the comparison. For example we want to sort the books first by title, then by the release year. We can sort them like the following. The lambda expression compares the release years only of the titles are equal, other wise it sorts according to the title.
Collections.sort(books, (b1, b2)->{ if(b1.getTitle().equals(b2.getTitle())) return b1.getReleaseYear()-b2.getReleaseYear(); return b1.getTitle().compareTo(b2.getTitle()); });