Last Updated on 17 June 2019   |   Print Email
There are several ways in order to sort a list collection by multiple attributes (keys) of its elements type. For example, sorting a list of employees by their job title, then by age, and then by salary. In this article, we are going to discuss the two common ways which are easy to understand and implement. That are, using a chained comparator, and using a CompareToBuilderclass of the Apache Commons Lang library.
1. The model class
Suppose that we have to manage a list of employees whose type is declared by the following class:
package net.codejava.collections;
public class Employee {
private String name;
private String jobTitle;
private int age;
private int salary;
public Employee(String name, String jobTitle, int age, int salary) {
this.name = name;
this.jobTitle = jobTitle;
this.age = age;
this.salary = salary;
}
// getters and setters
public String toString() {
return String.format("%s\t%s\t%d\t%d", name, jobTitle, age, salary);
}
}
Note that the Employee class overrides the toString() method in order to provide a meaningful information of an Employee object when printing the object using the System.out.println() method.
2. Using a chained Comparator
Create the EmployeeChainedComparator class looks like as follows:
package net.codejava.collections;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* This is a chained comparator that is used to sort a list by multiple
* attributes by chaining a sequence of comparators of individual fields
* together.
* @author www.codejava.net
*
*/
public class EmployeeChainedComparator implements Comparator<Employee> {
private List<Comparator<Employee>> listComparators;
@SafeVarargs
public EmployeeChainedComparator(Comparator<Employee>... comparators) {
this.listComparators = Arrays.asList(comparators);
}
@Override
public int compare(Employee emp1, Employee emp2) {
for (Comparator<Employee> comparator : listComparators) {
int result = comparator.compare(emp1, emp2);
if (result != 0) {
return result;
}
}
return 0;
}
}
The key points here are this comparator takes a list of comparators passed via its constructor; and the compare() method iterates over this comparators list to compare two Employee objects by each individual comparator. Now, we create separate comparators for each field by which we want to sort: job title, age and salary.Job title comparator:
package net.codejava.collections;
import java.util.Comparator;
/**
* This comparator compares two employees by their job titles.
* @author www.codejava.net
*
*/
public class EmployeeJobTitleComparator implements Comparator<Employee> {
@Override
public int compare(Employee emp1, Employee emp2) {
return emp1.getJobTitle().compareTo(emp2.getJobTitle());
}
}
Age comparator:
package net.codejava.collections;
import java.util.Comparator;
/**
* This comparator compares two employees by their ages.
* @author www.codejava.net
*
*/
public class EmployeeAgeComparator implements Comparator<Employee> {
@Override
public int compare(Employee emp1, Employee emp2) {
return emp1.getAge() - emp2.getAge();
}
}
Salary comparator:
package net.codejava.collections;
import java.util.Comparator;
/**
* This comparator compares two employees by their salaries.
* @author www.codejava.net
*
*/
public class EmployeeSalaryComparator implements Comparator<Employee> {
@Override
public int compare(Employee emp1, Employee emp2) {
return emp1.getSalary() - emp2.getSalary();
}
}
Now, write a test program with some dummy data as follows:
package net.codejava.collections;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* This program demonstrates how to sort a list collection by multiple
* attributes using a chained comparator.
*
* @author www.codejava.net
*
*/
public class SortingMultipleAttributesExample {
public static void main(String[] args) {
System.out.println("===== SORTING BY MULTIPLE ATTRIBUTES =====");
List<Employee> listEmployees = new ArrayList<Employee>();
listEmployees.add(new Employee("Tom", "Developer", 45, 80000));
listEmployees.add(new Employee("Sam", "Designer", 30, 75000));
listEmployees.add(new Employee("Bob", "Designer", 45, 134000));
listEmployees.add(new Employee("Peter", "Programmer", 25, 60000));
listEmployees.add(new Employee("Tim", "Designer", 45, 130000));
listEmployees.add(new Employee("Craig", "Programmer", 30, 52000));
listEmployees.add(new Employee("Anne", "Programmer", 25, 51000));
listEmployees.add(new Employee("Alex", "Designer", 30, 120000));
listEmployees.add(new Employee("Bill", "Programmer", 22, 30000));
listEmployees.add(new Employee("Samuel", "Developer", 28, 80000));
listEmployees.add(new Employee("John", "Developer", 35, 67000));
listEmployees.add(new Employee("Patrick", "Developer", 35, 140000));
listEmployees.add(new Employee("Alice", "Programmer", 35, 80000));
listEmployees.add(new Employee("David", "Developer", 35, 99000));
listEmployees.add(new Employee("Jane", "Designer", 30, 82000));
System.out.println("*** Before sorting:");
for (Employee emp : listEmployees) {
System.out.println(emp);
}
Collections.sort(listEmployees, new EmployeeChainedComparator(
new EmployeeJobTitleComparator(),
new EmployeeAgeComparator(),
new EmployeeSalaryComparator())
);
System.out.println("\n*** After sorting:");
for (Employee emp : listEmployees) {
System.out.println(emp);
}
}
}
Output:
===== SORTING BY MULTIPLE ATTRIBUTES =====
*** Before sorting:
Tom Developer 45 80000
Sam Designer 30 75000
Bob Designer 45 134000
Peter Programmer 25 60000
Tim Designer 45 130000
Craig Programmer 30 52000
Anne Programmer 25 51000
Alex Designer 30 120000
Bill Programmer 22 30000
Samuel Developer 28 80000
John Developer 35 67000
Patrick Developer 35 140000
Alice Programmer 35 80000
David Developer 35 99000
Jane Designer 30 82000
*** After sorting:
Sam Designer 30 75000
Jane Designer 30 82000
Alex Designer 30 120000
Tim Designer 45 130000
Bob Designer 45 134000
Samuel Developer 28 80000
John Developer 35 67000
David Developer 35 99000
Patrick Developer 35 140000
Tom Developer 45 80000
Bill Programmer 22 30000
Anne Programmer 25 51000
Peter Programmer 25 60000
Craig Programmer 30 52000
Alice Programmer 35 80000
As we can see, the list of employees is sorted firstly by job title, secondly by age, and finally salary, in ascending order.
3. Using a CompareToBuilder
The Apache Commons Lang API provides the CompareToBuilder class that can be used to sort a list collection by multiple attributes in a much simpler way. Just write a comparator as simple as follows:
package net.codejava.collections;
import java.util.Comparator;
import org.apache.commons.lang3.builder.CompareToBuilder;
/**
* This comparator sorts a list of Employees by job title, age and salary
* into ascending order.
* @author www.codejava.net
*
*/
public class EmployeeComparator implements Comparator<Employee> {
@Override
public int compare(Employee o1, Employee o2) {
return new CompareToBuilder()
.append(o1.getJobTitle(), o2.getJobTitle())
.append(o1.getAge(), o2.getAge())
.append(o1.getSalary(), o2.getSalary()).toComparison();
}
}
And sort the list using this comparator:
Collections.sort(listEmployees, new EmployeeComparator());
Output is as same as using the chained comparator above.And note that you have to put the commons-lang-VERSION.jar file to the classpath.
4. Switching sort order: ascending to descending
To reverse sort order from ascending to descending of a particular attribute, simply swap the order of the two objects being compared in the comparator. For example, the following comparator sorts the list by job title and age in ascending order, but by salary in descending order:
public class EmployeeComparator implements Comparator<Employee> {
@Override
public int compare(Employee o1, Employee o2) {
return new CompareToBuilder()
.append(o1.getJobTitle(), o2.getJobTitle())
.append(o1.getAge(), o2.getAge())
.append(o2.getSalary(), o1.getSalary()).toComparison();
}
}
When running the above test program with this comparator, the output will be:
Before sorting:
Tom Developer 45 80000
Sam Designer 30 75000
Bob Designer 45 134000
Peter Programmer 25 60000
Tim Designer 45 130000
Craig Programmer 30 52000
Anne Programmer 25 51000
Alex Designer 30 120000
Bill Programmer 22 30000
Samuel Developer 28 80000
John Developer 35 67000
Patrick Developer 35 140000
Alice Programmer 35 80000
David Developer 35 99000
Jane Designer 30 82000
After sorting:
Alex Designer 30 120000
Jane Designer 30 82000
Sam Designer 30 75000
Bob Designer 45 134000
Tim Designer 45 130000
Samuel Developer 28 80000
Patrick Developer 35 140000
David Developer 35 99000
John Developer 35 67000
Tom Developer 45 80000
Bill Programmer 22 30000
Peter Programmer 25 60000
Anne Programmer 25 51000
Craig Programmer 30 52000
Alice Programmer 35 80000
Nam Ha Minh is certified Java programmer (SCJP and SCWCD). He began programming with Java back in the days of Java 1.4 and has been passionate about it ever since. You can connect with him on Facebook and watch his Java videos on YouTube.
You're right, Filip. So to avoid potential overflow error, we should not use mathematical subtraction. Instead, we should use comparison (greater or less than).
Numeric values must not be compared using subtraction; for large values int may overflow. See: final int a1 = Integer.MAX_VALUE; final int a2 = Integer.MIN_VALUE; System.out.println(a1 - a2); System.out.println(Integer.compare(a1, a2));
Int.compare(int, int) or corresponding Long.compare(long, long) must be used.
Comments
So to avoid potential overflow error, we should not use mathematical subtraction. Instead, we should use comparison (greater or less than).
final int a1 = Integer.MAX_VALUE;
final int a2 = Integer.MIN_VALUE;
System.out.println(a1 - a2);
System.out.println(Integer.compare(a1, a2));
Int.compare(int, int) or corresponding Long.compare(long, long) must be used.