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

前端 未结 17 2019
醉酒成梦
醉酒成梦 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:30

    I know that it may be not directly answer to your question, because you said that null values have to be supported.

    But I just want to note that supporting nulls in compareTo is not in line with compareTo contract described in official javadocs for Comparable:

    Note that null is not an instance of any class, and e.compareTo(null) should throw a NullPointerException even though e.equals(null) returns false.

    So I would either throw NullPointerException explicitly or just let it be thrown first time when null argument is being dereferenced.

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

    See the bottom of this answer for updated (2013) solution using Guava.


    This is what I ultimately went with. It turned out we already had a utility method for null-safe String comparison, so the simplest solution was to make use of that. (It's a big codebase; easy to miss this kind of thing :)

    public int compareTo(Metadata other) {
        int result = StringUtils.compare(this.getName(), other.getName(), true);
        if (result != 0) {
            return result;
        }
        return StringUtils.compare(this.getValue(), other.getValue(), true);
    }
    

    This is how the helper is defined (it's overloaded so that you can also define whether nulls come first or last, if you want):

    public static int compare(String s1, String s2, boolean ignoreCase) { ... }
    

    So this is essentially the same as Eddie's answer (although I wouldn't call a static helper method a comparator) and that of uzhin too.

    Anyway, in general, I would have strongly favoured Patrick's solution, as I think it's a good practice to use established libraries whenever possible. (Know and use the libraries as Josh Bloch says.) But in this case that would not have yielded the cleanest, simplest code.

    Edit (2009): Apache Commons Collections version

    Actually, here's a way to make the solution based on Apache Commons NullComparator simpler. Combine it with the case-insensitive Comparator provided in String class:

    public static final Comparator<String> NULL_SAFE_COMPARATOR 
        = new NullComparator(String.CASE_INSENSITIVE_ORDER);
    
    @Override
    public int compareTo(Metadata other) {
        int result = NULL_SAFE_COMPARATOR.compare(this.name, other.name);
        if (result != 0) {
            return result;
        }
        return NULL_SAFE_COMPARATOR.compare(this.value, other.value);
    }
    

    Now this is pretty elegant, I think. (Just one small issue remains: the Commons NullComparator doesn't support generics, so there's an unchecked assignment.)

    Update (2013): Guava version

    Nearly 5 years later, here's how I'd tackle my original question. If coding in Java, I would (of course) be using Guava. (And quite certainly not Apache Commons.)

    Put this constant somewhere, e.g. in "StringUtils" class:

    public static final Ordering<String> CASE_INSENSITIVE_NULL_SAFE_ORDER =
        Ordering.from(String.CASE_INSENSITIVE_ORDER).nullsLast(); // or nullsFirst()
    

    Then, in public class Metadata implements Comparable<Metadata>:

    @Override
    public int compareTo(Metadata other) {
        int result = CASE_INSENSITIVE_NULL_SAFE_ORDER.compare(this.name, other.name);
        if (result != 0) {
            return result;
        }
        return CASE_INSENSITIVE_NULL_SAFE_ORDER.compare(this.value, other.value);
    }    
    

    Of course, this is nearly identical to the Apache Commons version (both use JDK's CASE_INSENSITIVE_ORDER), the use of nullsLast() being the only Guava-specific thing. This version is preferable simply because Guava is preferable, as a dependency, to Commons Collections. (As everyone agrees.)

    If you were wondering about Ordering, note that it implements Comparator. It's pretty handy especially for more complex sorting needs, allowing you for example to chain several Orderings using compound(). Read Ordering Explained for more!

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

    You can extract method:

    public int cmp(String txt, String otherTxt)
    {
        if ( txt == null )
            return otjerTxt == null ? 0 : 1;
    
        if ( otherTxt == null )
              return 1;
    
        return txt.compareToIgnoreCase(otherTxt);
    }
    
    public int compareTo(Metadata other) {
       int result = cmp( name, other.name); 
       if ( result != 0 )  return result;
       return cmp( value, other.value); 
    

    }

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

    If you want a simple Hack:

    arrlist.sort((o1, o2) -> {
        if (o1.getName() == null) o1.setName("");
        if (o2.getName() == null) o2.setName("");
    
        return o1.getName().compareTo(o2.getName());
    })
    

    if you want put nulls to end of the list just change this in above metod

    return o2.getName().compareTo(o1.getName());
    
    0 讨论(0)
  • 2020-11-28 18:34

    For the specific case where you know the data will not have nulls (always a good idea for strings) and the data is really large, you are still doing three comparisons before actually comparing the values, if you know for sure this is your case, you can optimize a tad bit. YMMV as readable code trumps minor optimization:

            if(o1.name != null && o2.name != null){
                return o1.name.compareToIgnoreCase(o2.name);
            }
            // at least one is null
            return (o1.name == o2.name) ? 0 : (o1.name != null ? 1 : -1);
    
    0 讨论(0)
  • 2020-11-28 18:36
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Comparator;
    
    public class TestClass {
    
        public static void main(String[] args) {
    
            Student s1 = new Student("1","Nikhil");
            Student s2 = new Student("1","*");
            Student s3 = new Student("1",null);
            Student s11 = new Student("2","Nikhil");
            Student s12 = new Student("2","*");
            Student s13 = new Student("2",null);
            List<Student> list = new ArrayList<Student>();
            list.add(s1);
            list.add(s2);
            list.add(s3);
            list.add(s11);
            list.add(s12);
            list.add(s13);
    
            list.sort(Comparator.comparing(Student::getName,Comparator.nullsLast(Comparator.naturalOrder())));
    
            for (Iterator iterator = list.iterator(); iterator.hasNext();) {
                Student student = (Student) iterator.next();
                System.out.println(student);
            }
    
    
        }
    
    }
    

    output is

    Student [name=*, id=1]
    Student [name=*, id=2]
    Student [name=Nikhil, id=1]
    Student [name=Nikhil, id=2]
    Student [name=null, id=1]
    Student [name=null, id=2]
    
    0 讨论(0)
提交回复
热议问题