How to simplify a null-safe compareTo() implementation?

前端 未结 17 2020
醉酒成梦
醉酒成梦 2020-11-28 18:03

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

相关标签:
17条回答
  • 2020-11-28 18:39

    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;
    }
    
    0 讨论(0)
  • 2020-11-28 18:40

    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
    
    0 讨论(0)
  • 2020-11-28 18:40

    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());
    }
    
    0 讨论(0)
  • 2020-11-28 18:43

    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.

    0 讨论(0)
  • 2020-11-28 18:48

    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.

    0 讨论(0)
  • 2020-11-28 18:50

    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

    0 讨论(0)
提交回复
热议问题