interface Comparator<T> {
int compare(T left, T right);
}
(and also its friend Comparable<T>
)
comparator.compareTo(a,b) < 0
iff a
precedes b
comparator.compareTo(a,b) > 0
iff b
precedes a
comparator.compareTo(a,b) == 0
iff a
and b
have equivalent positions
Long historical precendent, with "subtract" as the typical implementation; going back to at least the qsort()
function in C, CMP
instructions etc.
c(a,b) < 0
⇒ c(b,a) > 0
c(a,b) = 0
⇒ c(b,a) = 0
c(a,a) = 0
c(a,b) > 0 ∧ c(b,c) > 0
⇒ c(a,c) > 0
For classes where there is a "natural" ordering of all objects in the class, essentially a canonical Comparator
.
If a class implements Comparable
, it is recommended that a.compareTo(b) == 0
iff a.equals(b)
. This makes things like TreeSet
work as expected, in that they would retain the "same" elements as an equivalent HashSet
, for example.
Comparators often work by delegating to a sequence of properties in succession, returning the first (most significant) ordering found.
int compare(D left, D right) {
int comparison = openDateTimeFor(left).compareTo(openDateTimeFor(right);
if (comparison == 0) {
User leftOwner = ownerOf(left);
User rightOwner = ownerOf(right);
int leftIsCurrentUser = isCurrentUser(leftOwner);
int rightIsCurrentUser = isCurrentUser(leftOwner);
if (leftIsCurrentUser == 0 && rightIsCurrentUser != 0) {
comparison = 1;
} else if (rightIsCurrentUser == 0 && leftIsCurrentUser != 0) {
comparison = -1;
} else {
comparison = leftOwner.compareTo(rightOwner);
}
}
return comparison;
}
Comparator provides a factory based on extracting a property:
Comparator<D> compareByOpenDate =
Comparator.comparing(D::getOpenDateTime);
And also by extracting a property, and then applying another comparator to the extracted value:
Comparator<D> compareByOwnerName =
Comparator.comparing(D::getOwner,
Comparator.comparing(User::getName,
String.CASE_INSENSITIVE_ORDER));
Comparator also has default methods to produce a composed comparator that will perform an additional comparison:
Comparator<D> compareByOpenDateAndThenOwnerName =
Comparator.comparing(D::getOpenDateTime)
.thenComparing(D::getOwner,
Comparator.comparing(User::getName));
Comparator.comparing(D::getOpenDateTime)
.thenComparing(D::getOwner,
rankEqualFirst(signedOnUser)
.thenComparing(Comparator.naturalOrder()));
private <T> static Comparator<T> rankEqualFirst(T other) {
return Comparator.comparingInt(x -> x.equals(other) ? 0 : 1);
}
Note comparingInt
- there are int, double and long specializations of comparing
, thenCompare
to avoid unboxing
int compare(D left, D right) {
if (null != left && null != right) {
return caseInsensitiveCompare(getName(left), getName(right));
}
if (null == left && null != right) return -1;
if (null != left && null == right) return 1;
return 0;
}
private String getName(D obj) {
String name = obj.getName();
return Strings.isNullOrEmpty(name) ? "" : name;
}
Comparator.nullsFirst(
Comparator.comparing(D::getName,
Comparator.nullsFirst(
String.CASE_INSENSITIVE_ORDER)))
int compareNonEqual(D left, D right) {
if (rankHighly.contains(left)) return -1;
if (rankHighly.contains(right)) return 1;
if (left == RankLessHighly) return -1;
if (right == RankLessHighly) return 1;
if (left == RankEvenLessHighly) return 1;
if (right == RankEvenLessHighly) return -1;
if (left == RankLast) return 1;
if (right == RankLast) return -1;
return left.compareTo(right);
}
This breaks the expected antisymmetry behaviours: c(high1,high2) < 0
and c(high2,high1) < 0
int compare(D left, D right) {
// ... various comparisons
return left.equals(right) ? 0 : -1;
}
This also the expected antisymmetry behaviour: both c(a,b) < 0
and c(b,a) < 0
for any a
and b
making it to the final return
.
Want more examples?
ideas.git$ git log --since=1.jan.2015 -p --grep=Comparator