I\'m implementing compareTo()
method for a simple class such as this (to be able to use Collections.sort()
and other goodies offered by the Java pl
This is my implementation that I use to sort my ArrayList. the null classes are sorted to the last.
for my case, EntityPhone extends EntityAbstract and my container is List < EntityAbstract>.
the "compareIfNull()" method is used for null safe sorting. The other methods are for completeness, showing how compareIfNull can be used.
@Nullable
private static Integer compareIfNull(EntityPhone ep1, EntityPhone ep2) {
if (ep1 == null || ep2 == null) {
if (ep1 == ep2) {
return 0;
}
return ep1 == null ? -1 : 1;
}
return null;
}
private static final Comparator<EntityAbstract> AbsComparatorByName = = new Comparator<EntityAbstract>() {
@Override
public int compare(EntityAbstract ea1, EntityAbstract ea2) {
//sort type Phone first.
EntityPhone ep1 = getEntityPhone(ea1);
EntityPhone ep2 = getEntityPhone(ea2);
//null compare
Integer x = compareIfNull(ep1, ep2);
if (x != null) return x;
String name1 = ep1.getName().toUpperCase();
String name2 = ep2.getName().toUpperCase();
return name1.compareTo(name2);
}
}
private static EntityPhone getEntityPhone(EntityAbstract ea) {
return (ea != null && ea.getClass() == EntityPhone.class) ?
(EntityPhone) ea : null;
}
we can use java 8 to do a null-friendly comparasion between object. supposed i hava a Boy class with 2 fields: String name and Integer age and i want to first compare names and then ages if both are equal.
static void test2() {
List<Boy> list = new ArrayList<>();
list.add(new Boy("Peter", null));
list.add(new Boy("Tom", 24));
list.add(new Boy("Peter", 20));
list.add(new Boy("Peter", 23));
list.add(new Boy("Peter", 18));
list.add(new Boy(null, 19));
list.add(new Boy(null, 12));
list.add(new Boy(null, 24));
list.add(new Boy("Peter", null));
list.add(new Boy(null, 21));
list.add(new Boy("John", 30));
List<Boy> list2 = list.stream()
.sorted(comparing(Boy::getName,
nullsLast(naturalOrder()))
.thenComparing(Boy::getAge,
nullsLast(naturalOrder())))
.collect(toList());
list2.stream().forEach(System.out::println);
}
private static class Boy {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Boy(String name, Integer age) {
this.name = name;
this.age = age;
}
public String toString() {
return "name: " + name + " age: " + age;
}
}
and the result:
name: John age: 30
name: Peter age: 18
name: Peter age: 20
name: Peter age: 23
name: Peter age: null
name: Peter age: null
name: Tom age: 24
name: null age: 12
name: null age: 19
name: null age: 21
name: null age: 24
Another Apache ObjectUtils example. Able to sort other types of objects.
@Override
public int compare(Object o1, Object o2) {
String s1 = ObjectUtils.toString(o1);
String s2 = ObjectUtils.toString(o2);
return s1.toLowerCase().compareTo(s2.toLowerCase());
}
I always recommend using Apache commons since it will most likely be better than one you can write on your own. Plus you can then do 'real' work rather then reinventing.
The class you are interested in is the Null Comparator. It allows you to make nulls high or low. You also give it your own comparator to use when the two values are not null.
In your case you can have a static member variable that does the comparison and then your compareTo
method just references that.
Somthing like
class Metadata implements Comparable<Metadata> {
private String name;
private String value;
static NullComparator nullAndCaseInsensitveComparator = new NullComparator(
new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
// inputs can't be null
return o1.compareToIgnoreCase(o2);
}
});
@Override
public int compareTo(Metadata other) {
if (other == null) {
return 1;
}
int res = nullAndCaseInsensitveComparator.compare(name, other.name);
if (res != 0)
return res;
return nullAndCaseInsensitveComparator.compare(value, other.value);
}
}
Even if you decide to roll your own, keep this class in mind since it is very useful when ordering lists thatcontain null elements.
I would implement a null safe comparator. There may be an implementation out there, but this is so straightforward to implement that I've always rolled my own.
Note: Your comparator above, if both names are null, won't even compare the value fields. I don't think this is what you want.
I would implement this with something like the following:
// primarily by name, secondarily by value; null-safe; case-insensitive
public int compareTo(final Metadata other) {
if (other == null) {
throw new NullPointerException();
}
int result = nullSafeStringComparator(this.name, other.name);
if (result != 0) {
return result;
}
return nullSafeStringComparator(this.value, other.value);
}
public static int nullSafeStringComparator(final String one, final String two) {
if (one == null ^ two == null) {
return (one == null) ? -1 : 1;
}
if (one == null && two == null) {
return 0;
}
return one.compareToIgnoreCase(two);
}
EDIT: Fixed typos in code sample. That's what I get for not testing it first!
EDIT: Promoted nullSafeStringComparator to static.
In case anyone using Spring, there is a class org.springframework.util.comparator.NullSafeComparator that does this for you as well. Just decorate your own comparable with it like this
new NullSafeComparator<YourObject>(new YourComparable(), true)
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/comparator/NullSafeComparator.html