stream and lambda(10) - 中间操作之排序sorted与调试peek

语言: CN / TW / HK

Stream 操作过程中,有可能会对元素进行排序,这点 Stream 支持的也不输集合。考虑到流内操作只有在终止操作时才会触发导致不便调试,Stream 也提供了偷窃利器 peek 来方便我们调试。

sorted

方法定义

Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);

sorted 提供了两个方法,一个无参,一个需要传入比较器。两种方法运行过后,都可以将流内的元素排序。

使用举例

根据文档来看,sorted() 会将流内的元素进行自然排序,同时要求元素必须实现 Comparable 接口。看到这里,问题就来了,什么是自然排序?为什么要实现 Comparable 接口?我们来实验一下。

public void sortedStringTest() {
    List<String> list = Arrays.asList("i", "am", "sorted");
    System.out.println(list.stream().sorted().collect(Collectors.toList()));
}

程序输出:

[am, i, sorted]

看来,确实按照字母的顺序来排序了。可是,如果元素没有实现 Comparable 又是什么情况呢?

public void sortedNotComparableTest() {
    List<StudentNotComparable> students = Arrays.asList(new StudentNotComparable("zhangsan", 15), new StudentNotComparable("lisi", 23));
    students.stream().sorted().forEach(s -> System.out.println(s.getAge()));
}

public class StudentNotComparable {
    private String name;
    private int age;
}

大事不妙,程序抛异常了:

Exception in thread “main” java.lang.ClassCastException: StudentNotComparable cannot be cast to java.lang.Comparable at java.util.Comparators$NaturalOrderComparator.compare(Comparators.java:47) at java.util.TimSort.countRunAndMakeAscending(TimSort.java:355) at java.util.TimSort.sort(TimSort.java:220) at java.util.Arrays.sort(Arrays.java:1512)

LowB 的我决定一探源码究竟。经过几步跳转,找到如下关键代码:

OfRef(AbstractPipeline<?, T, ?> upstream) {
    ...//此处省略
    Comparator<? super T> comp = (Comparator<? super T>) Comparator.naturalOrder();
    this.comparator = comp;
}

好家伙,原来,排序过程中需要比较器。那从元素对象身上又是怎么取到比较器的呢?

enum NaturalOrderComparator implements Comparator<Comparable<Object>> {
    ...//此处省略
    @Override
    public int compare(Comparable<Object> c1, Comparable<Object> c2) {
        return c1.compareTo(c2);
    }
}

看来,猜测的没错了, 之所以需要元素对象实现 Comparable ,就是要用 compareTo 方法来排序 ,这就是所谓的自然排序。

好了,明白了,我们再来看一下带有 Comparable 时运行结果。

public void sortedComparableTest() {
    List<StudentComparable> students = Arrays.asList(new StudentComparable("zhangsan", 15), new StudentComparable("lisi", 23));
    students.stream().sorted().forEach(s -> System.out.println(s.getAge()));
}

public class StudentComparable implements Comparable<StudentComparable> {
    private String name;
    private int age;

    @Override
    public int compareTo(StudentComparable o) {
        return this.age - o.getAge();
    }
}

此时,运行正确,程序依次输出 15,23 两个数字。

sorted(Comparator<? super T> comparator)

经过了上面的原理探究,这个带有比较器方法的原理盲猜也可以猜到了:直接使用传入的比较器进行排序。来,试验一下。

public void sortedWithComparatorTest() {
    List<StudentNotComparable> students = Arrays.asList(new StudentNotComparable("zhangsan", 15), new StudentNotComparable("lisi", 23));
    students.stream().sorted((s1, s2) -> s1.getAge() - s2.getAge()).forEach(s -> System.out.println(s.getAge()));
}

果然,如果元素未实现 Comparable 接口,传入比较器之后,程序就正常运行了,依次输出 15,23 两个数字。

你以为至此就结束了吗?No,此时的我忽然冒出了另一个想法,如果元素实现了 Comparable ,此时又传入了比较器,排序时以哪个为准? 盲猜一下: 以传入的比较器为准,毕竟走的是传了比较器的方法。

public void sortedWithComparatorTest2() {
    List<StudentComparable> students = Arrays.asList(new StudentComparable("zhangsan", 15), new StudentComparable("lisi", 23));
    students.stream().sorted((s1, s2) -> s2.getAge() - s1.getAge()).forEach(s -> System.out.println(s.getAge()));
}

验证了一下,传入一个和 Comparable 相反的比较器,程序依次输出 23,15 两个数字,我们的盲猜是正确的。

为什么我能猜这么准?因为以前我们研究过 TreeMap 啊,原理都是一样的: Java 集合分析之 Map - 这个 Map 有顺序(LinkedHashMap 和 TreeMap)

peek

方法定义

Stream<T> peek(Consumer<? super T> action);

peek 方法可以将流元素取出并进行消费。看起来和 foreach 是一样,都传一个 Comsumer ,不同的是, peek 方法然后返回 Stream ,这也就意味着,我们对流进行取值,不影响流内元素继续流转,所以就可以用作 debug 了。

使用举例

public void peekTest() {
    List<String> list = Arrays.asList("i", "am", "sorted");
    List<Integer> totalLength = list.stream().map(String::length).peek(System.out::println).collect(Collectors.toList());
    System.out.println(totalLength);
}

在这个过程中,我们先取出了各个字符串的长度并打印,最后,将所有长度转成一个 List,并最终打印。

1 2 6 [1, 2, 6]

总结

  • sorted 方法可以对流内的元素进行排序
  • 如果不传比较器,sorted 的元素需实现 Comparable,排序按照 compareTo 方法来排序
  • 如果 sorted 方法传入了比较器,排序时以比较器为准
  • 偷窃利器 peek 可以用于对流 debug,peek 不影响程序运行中间状态及最终结果

注:本文配套代码可在 github 查看: stream-and-lambda