The Comparable interface that we looked at in the previous section defines a default ordering for the objects of a class. This default ordering is also called the natural ordering of the objects.
But what if you need to alter the default ordering just for a single requirement? For example, what if you want to sort the Employee objects in the previous example based on their names, not IDs?
You can’t change the implementation of the compareTo() function because it will affect the ordering everywhere, not just for your particular requirement.
Also, If you’re dealing with a predefined Java class or a class defined in a third party library, you won’t be able to change the default ordering. For example, The default ordering of String objects is to order them alphabetically. But what if you want to order them based on their length?
For such cases, Java provides a Comparator interface. You can define a Comparator and pass it to the sorting functions like Collections.sort or Arrays.sort to sort the objects based on the ordering defined by the Comparator.
The Comparator interface contains a method called compare() that you need to implement in order to define the ordering of the objects of a class -
The implementation of the compare() method should return
a negative integer, if the first argument is less than the second,
zero, if the first argument is equal to the second, and
a positive integer, if the first argument is greater than the second.
Let’s see an example to make things clear.
How does Collections.Sort() work?
Internally the Sort method does call Compare method of the classes it is sorting. To compare two elements, it asks “Which is greater?” Compare method returns -1, 0 or 1 to say if it is less than, equal, or greater to the other. It uses this result to then determine if they should be swapped for its sort.
Working Program:
// Java program to demonstrate working of Comparator // interface importjava.util.ArrayList;importjava.util.Collections;importjava.util.Comparator;// A class to represent a student. classStudent {int rollno;String name, address;// ConstructorpublicStudent(int rollno,String name,String address) {this.rollno= rollno;this.name= name;this.address= address; }// Used to print student details in main()publicStringtoString() {returnthis.rollno+" "+this.name+" "+this.address; }}classSortbyrollimplementsComparator<Student> {// Used for sorting in ascending order of// roll numberpublicintcompare(Student a,Student b) {returna.rollno-b.rollno; }}classSortbynameimplementsComparator<Student> {// Used for sorting in ascending order of// roll namepublicintcompare(Student a,Student b) {returna.name.compareTo(b.name); }}// Driver classpublicclassMain {publicstaticvoidmain(String[] args) {ArrayList<Student> ar =newArrayList<Student>();ar.add(newStudent(111,"bbbb","london"));ar.add(newStudent(131,"aaaa","nyc"));ar.add(newStudent(121,"cccc","jaipur"));System.out.println("Unsorted");for (int i =0; i <ar.size(); i++)System.out.println(ar.get(i));Collections.sort(ar,newSortbyroll());System.out.println("\nSorted by rollno");for (int i =0; i <ar.size(); i++)System.out.println(ar.get(i));Collections.sort(ar,newSortbyname());System.out.println("\nSorted by name");for (int i =0; i <ar.size(); i++)System.out.println(ar.get(i)); }}
Unsorted
111 bbbb london
131 aaaa nyc
121 cccc jaipur
Sorted by rollno
111 bbbb london
121 cccc jaipur
131 aaaa nyc
Sorted by name
131 aaaa nyc
111 bbbb london
121 cccc jaipur
Sort collection by more than one field:
In previous articles, we have discussed how to sort list of objects on the basis of single field using Comparable and Comparator interface But, what if we have a requirement to sort ArrayList objects in accordance with more than one fields like firstly sort, according to student name and secondly sort according to student roll number.
Below is the implementation of above approach:
// Java program to demonstrate working of Comparator // interface importjava.util.ArrayList;importjava.util.Collections;importjava.util.Comparator;// A class to represent a student. classStudent {Integer rollno;String name, address;publicIntegergetRollno() {return rollno; }publicvoidsetRollno(Integer rollno) {this.rollno= rollno; }publicStringgetName() {return name; }publicvoidsetName(String name) {this.name= name; }publicStringgetAddress() {return address; }publicvoidsetAddress(String address) {this.address= address; }publicStudent(Integer rollno,String name,String address) { super();this.rollno= rollno;this.name= name;this.address= address; }// Used to print student details in main()publicStringtoString() {returnthis.rollno+" "+this.name+" "+this.address; }}classSortByNameAndRollNumimplementsComparator<Student> {// Used for sorting in ascending order of// roll namepublicintcompare(Student a,Student b) {// for comparisonint NameCompare =a.getName().compareTo(b.getName());int RollNumCompare =a.getRollno().compareTo(b.getRollno());// 2-level comparison using if-else blockif (NameCompare ==0) {return ((RollNumCompare ==0) ? NameCompare : RollNumCompare); } else {return NameCompare; } }}// Driver classpublicclassMain {publicstaticvoidmain(String[] args) {ArrayList<Student> ar =newArrayList<Student>();ar.add(newStudent(111,"aaaa","london"));ar.add(newStudent(131,"aaaa","nyc"));ar.add(newStudent(121,"cccc","jaipur"));System.out.println("Unsorted");for (int i =0; i <ar.size(); i++)System.out.println(ar.get(i));Collections.sort(ar,newSortByNameAndRollNum());System.out.println("\nSorted by namd and rolNum");for (int i =0; i <ar.size(); i++)System.out.println(ar.get(i)); }}
Unsorted
111 aaaa london
131 aaaa nyc
121 cccc jaipur
Sorted by namd and rolNum
111 aaaa london
131 aaaa nyc
121 cccc jaipur
As you saw in the above example, All the sorting methods also accept an instance of a Comparatorinterface. They use the ordering defined by the Comparator interface’s compare() function to sort the objects.
2) Using Java 8 Comparator default methods
The Comparator interface contains various default factory methods for creating Comparator instances.
All the Comparators that we created in the previous section can be made more concise by using these factory methods.
Here is the same Comparator example that we saw in the previous section using Java 8 Comparator default methods -
importjava.time.LocalDate;importjava.util.ArrayList;importjava.util.Collections;importjava.util.Comparator;importjava.util.List;publicclassComparatorExample {publicstaticvoidmain(String[] args) {List<Employee> employees =newArrayList<>();employees.add(newEmployee(1010,"Rajeev",100000.00,LocalDate.of(2010,7,10)));employees.add(newEmployee(1004,"Chris",95000.50,LocalDate.of(2017,3,19)));employees.add(newEmployee(1015,"David",134000.00,LocalDate.of(2017,9,28)));employees.add(newEmployee(1009,"Steve",100000.00,LocalDate.of(2016,5,18)));System.out.println("Employees : "+ employees);// Sort employees by NameCollections.sort(employees,Comparator.comparing(Employee::getName));System.out.println("\nEmployees (Sorted by Name) : "+ employees);// Sort employees by SalaryCollections.sort(employees,Comparator.comparingDouble(Employee::getSalary));System.out.println("\nEmployees (Sorted by Salary) : "+ employees);// Sort employees by JoiningDateCollections.sort(employees,Comparator.comparing(Employee::getJoiningDate));System.out.println("\nEmployees (Sorted by JoiningDate) : "+ employees);// Sort employees by Name in descending orderCollections.sort(employees,Comparator.comparing(Employee::getName).reversed());System.out.println("\nEmployees (Sorted by Name in descending order) : "+ employees);// Chaining multiple Comparators// Sort by Salary. If Salary is same then sort by NameCollections.sort(employees,Comparator.comparingDouble(Employee::getSalary).thenComparing(Employee::getName));System.out.println("\nEmployees (Sorted by Salary and Name) : "+ employees); }}