Reputation: 47
How to sort all fields in an object using a single comparator?
Ex: If I have an Employee
object with three fields such as Name
, Eid
and Salary
. Instead of writing three comparators i.e. Namecomparator
, Eidcomparator
and Salarycomparator
for sorting, I need only only one comparator that can sort based on the filed I provide dynamically.
Upvotes: 2
Views: 1132
Reputation: 82899
Instead of writing 3 comparator's i.e., Namecomparotor, Eidcomparotor and Salarycomparotor for sorting, I need only only one comparotor that can sort based on the filed I provide dynamically.
Using Java 8, and if all those fields have a get
method, you can use Comparator.comparing
Collections.sort(employees, Comparator.comparing(Employee::getSalary);
And if you want to sort by multiple criteria (to break ties), you can use thenComparing
Collections.sort(employees, Comparator.comparing(Employee::getSalary)
.thenComparing(Employee::getName)
.thenComparing(Employee::getId));
In both cases, you just pass the field to sort by as a Function
to create a custom Comparator
. Alternatively, you can also use a lambda
expression to sort by all kinds of other criteria:
Collections.sort(employees, Comparator.comparing(e -> e.getName.length()));
In case you are using an older version of Java (and can not upgrade to Java 8), you could make your own Function
class and an according generic Comparator
:
abstract class CompareFunction<A> {
abstract public Comparable apply(A object);
}
class FunctionComparator<T> implements Comparator<T> {
final CompareFunction<T> function;
public FunctionComparator(CompareFunction<T> function) {
this.function = function;
}
public int compare(T a, T b) {
return function.apply(a).compareTo(function.apply(b));
}
}
Usage:
Collections.sort(employees, new FunctionComparator<Employee>(new CompareFunction<Employee>() {
public Comparable apply(Employee object) {
return object.getSalary();
};
}));
Or combine it with Java Reflextion to create such a comparator based on the name of a getter method:
public static <T> Comparator<T> createGetterComparator(Class<T> clazz, String getterName) throws Exception {
final Method getter = clazz.getMethod(getterName);
return new FunctionComparator<T>(new CompareFunction<T>() {
public Comparable apply(T object) {
try {
return (Comparable) getter.invoke(object);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
});
}
Usage:
Collections.sort(employees, createGetterComparator(Employee.class, "getSalary"));
Upvotes: 9
Reputation: 37023
Use something like sort by id, then name then salary:
public class EmployeeComparator implements Comparator<Employee> {
public int compare(Employee e1, Employee e2) {
if (e1 == e2) {
return 0;
}
if (e1 == null && e2 != null) {
return -1;
}
if (e1 != null && e2 == null) {
return -1;
}
if (e1.getId().equals(e2.getId())) {
if (e1.getName().equals(e2.getName())) {
if (e1.getSalary() == e2.getSalary()) {
return 0;
} else {
return int(e1.getSalary() - e2.getSalary());
}
} else {
return e1.getName().compareTo(e2.getName());
}
} else {
return e1.getId().compareTo(e2.getId());
}
}
}
If you need concise version of above, you will need to either use Guava's ComparisonChain as below:
@Override
public int compare(Employee e1, Employee e2) {
return ComparisonChain.start()
.compare(e1.getId(), e2.getId(), Ordering.natural().nullsLast())
.compare(e1.getName(), e2.getName(), Ordering.natural().nullsLast())
.compare(e1.getSalary(), e2.getSalary(), Ordering.natural().nullsLast())
.result();
}
or apache's CompareToBuilder as below:
public int compare(Employee e1, Employee e2) {
return new CompareToBuilder().append(e1.getId(), e2.getId()).append(e1.getName(), e2.getName()).append(e1.getSalary(), e2.getSalary()).toComparison();
}
Upvotes: 5