Comparator.nullsLast 空指针问题

背景

线上报了个NPE,定位问题是nullsLast排序时从map里取到的值为null导致,但既然使用了nullsLast说明开发在写代码时对空指针是有防御意识的,虽然实际没有起到预期的效果。
仔细看了下代码发现大家对nullsLast/nullsFirst的理解并不深,有不少代码写法有问题,导致并不能起到想要的效果。

问题

List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5, 6);
Map<Integer, Integer> map = new HashMap<>();
map.put(4, 400);
map.put(5, 500);
map.put(6, 600);

// Q1: 下面这行代码nullsLast报NPE了,nullsLast应该怎么用?
list.sort(Comparator.nullsLast(Comparator.comparing(map::get)));


// Q2: 下面两行代码的执行结果分别是什么?
list.sort(Comparator.comparing(map::get, Comparator.nullsLast(Integer::compareTo).reversed()));
list.sort(Comparator.comparing(map::get, Comparator.nullsLast(Comparator.reverseOrder())));

结论

  1. nullsLast 和 comparing的顺序要注意,nullsLast要用于可能产生npe的对象上才有效果
  2. nullsLast 参数中的comparator用于比较非null值,为null的值认为是相等的
  3. .reversed() 方法将比较器的排序翻转,Comparator.reverseOrder() 参数将nullsLast中非null数据的排序翻转
    具体Case结合下述代码分析,重点关注Case 1、3、4、6
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNullPointerException;

@Test
void comparatorNullsLastNpeTest() {
    List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5, 6);
    Map<Integer, Integer> map = new HashMap<>();
    map.put(4, 400);
    map.put(5, 500);
    map.put(6, 600);

    // Case 1: NPE
    List<Integer> finalList = list;
    assertThatNullPointerException().isThrownBy(() ->
            finalList.sort(Comparator.nullsLast(Comparator.comparing(map::get))));

    // Case 2: NPE
    assertThatNullPointerException().isThrownBy(() ->
            finalList.stream()
                    .sorted(Comparator.nullsLast(Comparator.comparing(map::get)))
                    .collect(Collectors.toList()));

    // Case 3: Correct
    list = Lists.newArrayList(1, 2, 3, 4, 5, 6);
    list.sort(Comparator.comparing(map::get, Comparator.nullsLast(Integer::compareTo)));
    /// 1,2,3 map对应value为null,放到末尾
    assertThat(list).isEqualTo(new ArrayList<>(Arrays.asList(4, 5, 6, 1, 2, 3)));

    // Case 4: Correct
    list = Lists.newArrayList(1, 2, 3, 4, 5, 6);
    list.sort(Comparator.comparing(map::get, Comparator.nullsLast(Integer::compareTo).reversed()));
    /// 4,5,6倒序变成6,5,4,[1,2,3]都为null看作整体,nullsLast倒序,变成[1,2,3],6,5,4
    assertThat(list).isEqualTo(new ArrayList<>(Arrays.asList(1, 2, 3, 6, 5, 4)));

    // Case 5: Correct
    list = Lists.newArrayList(1, 2, 3, 4, 5, 6);
    list.sort(Comparator.comparing(map::get, Comparator.nullsLast(Integer::compareTo)).reversed());
    /// 同Case 4
    assertThat(list).isEqualTo(new ArrayList<>(Arrays.asList(1, 2, 3, 6, 5, 4)));

    // Case 6: Correct
    list = Lists.newArrayList(1, 2, 3, 4, 5, 6);
    list.sort(Comparator.comparing(map::get, Comparator.nullsLast(Comparator.reverseOrder())));
    /// nullsLast 参数中的comparator用于比较非null值,4,5,6逆序得到6,5,4, 然后1,2,3作为null放末尾
    assertThat(list).isEqualTo(new ArrayList<>(Arrays.asList(6, 5, 4, 1, 2, 3)));

    // Case 7: Correct
    List<Integer> sortedList = list.stream()
            .sorted(Comparator.comparing(map::get, Comparator.nullsLast(Integer::compareTo)))
            .collect(Collectors.toList());
    assertThat(sortedList).isEqualTo(new ArrayList<>(Arrays.asList(4, 5, 6, 1, 2, 3)));

    // Case 8: Correct
    List<Integer> sortedListReversed = list.stream()
            .sorted(Comparator.comparing(map::get, Comparator.nullsLast(Integer::compareTo).reversed()))
            .collect(Collectors.toList());
    /// 同Case 4
    assertThat(sortedListReversed).isEqualTo(new ArrayList<>(Arrays.asList(1, 2, 3, 6, 5, 4)));

    // Case 9: Correct
    List<Integer> sortedListReversed1 = list.stream()
            .sorted(Comparator.comparing(map::get, Comparator.nullsLast(Comparator.reverseOrder())))
            .collect(Collectors.toList());
    /// 同Case 6
    assertThat(sortedListReversed1).isEqualTo(new ArrayList<>(Arrays.asList(6, 5, 4, 1, 2, 3)));
}
  • Case 1中comparing的参数不能为null,否则会报NPE;
  • Case 3 中先执行nullsLast排序,再执行comparing,没有NPE问题;
  • Case 4 对nullsLast比较器的最终结果进行翻转,同时会对入参comparator进行翻转(见下图中reversed方法)
  • Case 6 Comparator.reverseOrder()作为nullsLast的入参,仅对非null数据进行翻转,不影响为null的数据的顺序
    Comparator.nullsLast 空指针问题_第1张图片

扩展

  1. TimSort:Java和Python内置排序算法
  • 世界上最快的排序算法——Timsort
  • Timsort — the fastest sorting algorithm you’ve never heard of
  1. 线上故障之Comparator IllegalArgumentException: Comparison method violates its general contract!
    在 JDK7 版本以上,Comparator 要满足自反性、传递性、对称性。否则会报上述异常。
    Comparator.nullsLast 空指针问题_第2张图片

你可能感兴趣的:(Coding,java,nullsLast,空指针,Comparator)