Nice general way to sort nulls to the bottom, regardless?

后端 未结 5 515
北恋
北恋 2021-01-02 10:53

I\'m writing some custom Comparators, and I\'d like them to push null items to the bottom of the list, regardless of whether I\'m sorting ascending or descending. What\'s a

相关标签:
5条回答
  • 2021-01-02 11:22

    In Java 8, you can use the Comparator.nullsLast and Comparator.nullsFirst static methods to have more null-friendly comparators. Suppose you have a Fruit class like the following:

    public class Fruit {
        private final String name;
        private final Integer size;
    
        // Constructor and Getters
    }
    

    If you want to sort a bunch of fruits by their size and put the nulls at the end:

    List<Fruit> fruits = asList(null, new Fruit("Orange", 25), new Fruit("Kiwi", 5));
    

    You can simply write:

    Collections.sort(fruits, Comparator.nullsLast(Comparator.comparingInt(Fruit::getSize)));
    

    And the result would be:

    [Fruit{name='Kiwi', size=5}, Fruit{name='Orange', size=25}, null]
    
    0 讨论(0)
  • 2021-01-02 11:27

    Following up on dfa's answer - what I want is that the nulls sort at the end without affecting the order of the non-nulls. So I want something more along the lines of this:

    public class NullComparatorsTest extends TestCase {
        Comparator<String>  forward = new Comparator<String>() {
                                        public int compare(String a, String b) {
                                            return a.compareTo(b);
                                        }
                                    };
    
        public void testIt() throws Exception {
            List<String> strings = Arrays.asList(null, "aaa", null, "bbb", "ccc", null);
            Collections.sort(strings, NullComparators.atEnd(forward));
            assertEquals("[aaa, bbb, ccc, null, null, null]", strings.toString());
            Collections.sort(strings, NullComparators.atBeginning(forward));
            assertEquals("[null, null, null, aaa, bbb, ccc]", strings.toString());
        }
    }
    
    public class NullComparators {
        public static <T> Comparator<T> atEnd(final Comparator<T> comparator) {
            return new Comparator<T>() {
                public int compare(T a, T b) {
                    if (a == null && b == null)
                        return 0;
                    if (a == null)
                        return 1;
                    if (b == null)
                        return -1;
                    return comparator.compare(a, b);
                }
            };
        }
    
        public static <T> Comparator<T> atBeginning(final Comparator<T> comparator) {
            return new Comparator<T>() {
                public int compare(T a, T b) {
                    if (a == null && b == null)
                        return 0;
                    if (a == null)
                        return -1;
                    if (b == null)
                        return 1;
                    return comparator.compare(a, b);
                }
            };
        }
    }
    

    Full credit to dfa, though - this is just a minor modification of his work.

    0 讨论(0)
  • 2021-01-02 11:35

    I agree with Jon Skeet (it's so easy :). I tried to implement a very simple decorator:

    class NullComparators {
    
        static <T> Comparator<T> atEnd(final Comparator<T> comparator) {
            return new Comparator<T>() {
    
                public int compare(T o1, T o2) {
                    if (o1 == null && o2 == null) {
                        return 0;
                    }
    
                    if (o1 == null) {
                        return 1;
                    }
    
                    if (o2 == null) {
                        return -1;
                    }
    
                    return comparator.compare(o1, o2);
                }
            };
        }
    
        static <T> Comparator<T> atBeginning(final Comparator<T> comparator) {
            return Collections.reverseOrder(atEnd(comparator));
        }
    }
    

    given a Comparator:

    Comparator<String> wrapMe = new Comparator<String>() {
          public int compare(String o1, String o2) {
              return o1.compareTo(o2);
          }
    };
    

    and some test data:

    List<String> strings = Arrays.asList(null, "aaa", null, "bbb", "ccc", null);
    

    you can sort with nulls at end:

    Collections.sort(strings, NullComparators.atEnd(wrapMe));
    
    [aaa, bbb, ccc, null, null, null]
    

    or at beginning:

    Collections.sort(strings, NullComparators.atBeginning(wrapMe));
    
    [null, null, null, ccc, bbb, aaa]
    
    0 讨论(0)
  • 2021-01-02 11:45

    The last option appeals to me a lot. Comparators are really great to chain together. In particular you may well want to write a ReverseComparator as well as a NullWrappingComparator.


    EDIT: You don't have to write this yourself. If you look at the Ordering class in the Google Collections Library you'll find this and all kinds of other goodies :)


    EDIT: Going into more detail to show what I mean about ReverseComparator...

    One word of warning - in the implementation of a ReverseComparator, reverse the order of the arguments instead of negating the result, as otherwise Integer.MIN_VALUE is "reversed" to itself.

    So this implementation is wrong (assuming original is the comparator to reverse):

    public int compare(T x, T y)
    {
        return -original.compare(x, y);
    }
    

    but this is right:

    public int compare(T x, T y)
    {
        return original.compare(y, x);
    }
    

    The reason is that we always want to reverse the comparison, but if original.compare(x, y) returns int.MIN_VALUE, then the bad comparer will also return int.MIN_VALUE, which is incorrect. This is due to the funny property that int.MIN_VALUE == -int.MIN_VALUE.

    0 讨论(0)
  • 2021-01-02 11:45

    You could always use NullComparator from commons-collections. It's been around longer than Google Collections.

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