0%

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

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

sorted

方法定义

Stream<T> sorted();

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

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

使用举例

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

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

程序输出:

[am, i, sorted]

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

1
2
3
4
5
6
7
8
9
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 的我决定一探源码究竟。经过几步跳转,找到如下关键代码:

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)

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

1
2
3
4
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,此时又传入了比较器,排序时以哪个为准?盲猜一下:以传入的比较器为准,毕竟走的是传了比较器的方法。

1
2
3
4
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 了。

使用举例

1
2
3
4
5
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