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
介面?我們來實驗一下。
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
- stream and lambda(18) - 終止操作之 stream 收集器 collectors
- stream and lambda(15) - 終止操作之 stream 陣列操作 toArray
- stream and lambda(13) - 終止操作之計數 count 與比較 min、max
- stream and lambda(12) - 終止操作之查詢與匹配(findAny、findFirst、allMatch、anyMatch、noneMatch)
- stream and lambda(11) - Optional 簡介
- stream and lambda(10) - 中間操作之排序sorted與除錯peek
- stream and lambda(9) - 中間操作之map操作(map、flatmap)
- stream and lambda(8) - 中間操作之篩選操作(filter、distinct、limit、skip)
- stream and lambda(7) - stream 的建立
- stream and lambda(6) - stream 簡介
- stream and lambda(5) - lambda 表示式最佳實踐
- stream and lambda(3) - jdk 提供的函式式介面
- stream and lambda(2) - 函式式介面簡介
- stream and lambda(1) - lambda 簡介