Some time ago a fellow developer wrote a really comprehensive blog post (unfortunately only available in german) about comparator implementations in Java. More specifically it is about composite comparators used to compare entities according to different attributes. The best solution for Java 7 he comes up with is a comparator
class FoobarComparator implements Comparator { @Override public int compare(Foobar lhs, Foobar rhs) { return compoundCompare( lhs.getLastName().compareTo(rhs.getLastName()), lhs.getFirstName().compareTo(rhs.getFirstName()), lhs.getPlaceOfBirth().compareTo(rhs.getPlaceOfBirth()), lhs.getDateOfBirth().compareTo(rhs.getDateOfBirth())); } }
with a reusable compoundCompare()-method
// utility method used with static import int compoundCompare(int... results) { for (int result : results) { if (result != 0) { return result; } } return 0; }
While this solution is quite clean and a vast improvement over the critized implementations it has the flaw that it eagerly evaluates all attributes even though short-circuiting may be possible for many entities. This may lead to performance problems in some cases. So he goes on to explain how Java 8 will fix this problem with Lambdas or another solution he calls “KeyMethodComparator”.
Now I want to show you an implementation very similar to his approach above but without the performance penalty and possible in Java 7 using the composite pattern:
import java.util.Arrays; import java.util.Comparator; import java.util.List; class FoobarComparator implements Comparator<Foobar> { private List<Comparator<Foobar>> defaultFoobarComparison = Arrays.<Comparator<Foobar>>asList( new Comparator<Foobar>() { @Override public int compare(Foobar lhs, Foobar rhs) { return lhs.getLastName().compareTo(rhs.getLastName()); } }, new Comparator<Foobar>() { @Override public int compare(Foobar lhs, Foobar rhs) { return lhs.getFirstName().compareTo(rhs.getFirstName()); } }, new Comparator<Foobar>() { @Override public int compare(Foobar lhs, Foobar rhs) { return lhs.getPlaceOfBirth().compareTo(rhs.getPlaceOfBirth()); } }, new Comparator<Foobar>() { @Override public int compare(Foobar lhs, Foobar rhs) { return lhs.getDateOfBirth().compareTo(rhs.getDateOfBirth()); } }); @Override public int compare(Foobar lhs, Foobar rhs) { for (Comparator<Foobar> comp : defaultFoobarComparison) { int result = comp.compare(lhs, rhs); if (result != 0) { return result; } } return 0; } }
It features the lazy evaluation demanded by my fellow for performance and allows flexible construction of different composite comparators if you, e.g. add a constructor accepting a list of comparators.
Imho, it is a quite elegant solution using standard object-oriented programming in Java today and not only in the future.
Hmm, why not make it shorter, like so?
public class GenericCompositeComparator implements Comparator{
Comparator[] comparators;
public GenericCompositeComparator(Comparator… comparators) {
this.comparators = comparators;
}
@Override
public int compare(V lhs, V rhs) {
for (Comparator comp : comparators) {
int result = comp.compare(lhs, rhs);
if (result != 0) {
return result;
}
}
return 0;
}
}
and use it like so:
public class FoobarComparator extends GenericCompositeComparator {
@SuppressWarnings(“unchecked”)
public FoobarComparator() {
super(
new Comparator() {
@Override
public int compare(Foobar lhs, Foobar rhs) {
return lhs.getLastName().compareTo(rhs.getLastName());
}
},
new Comparator() {
@Override
public int compare(Foobar lhs, Foobar rhs) {
return lhs.getFirstName().compareTo(rhs.getFirstName());
}
});
}
}
Now imagine that with lamda expressions or method references…
Yes, you are right. Your refactoring takes the whole approach one step further resulting in a generic and reusable CompositeComparator. I wanted to stay as close as possible to the original code in the mentioned blog.
Lambdas will reduce noise dramatically in such cases so I am really looking forward to them….
Oops, these classes were supposed to use generics, but the types were stripped during posting.
The class was supposed to use a type T and the constructor to accept comparators of ? super T
/**
* The purpose of this class is to ease implementation of {@link Comparable#compareTo(Object)}
* or {@link java.util.Comparator#compare(Object, Object)} when we need to compare multiple
* attributes.
*
* Example:
*
* @param pair type
*/
public class Pair {
private final Comparable p1;
private final T p2;
public Pair(Comparable p1, T p2) {
this.p1 = p1;
this.p2 = p2;
}
public int compare() {
if (p1 == null && p2 == null) {
return 0;
}
if (p1 == null) {
return -1;
}
if (p2 == null) {
return 1;
}
return p1.compareTo(p2);
}
public static int compare(Pair[] pairs) {
for (Pair pair : pairs) {
int result = pair.compare();
if (result != 0) {
return result;
}
}
return 0;
}
}