「Java8系列」神祕的Lambda
「Java8系列」神奇的函數式接口
繼上兩篇以後,本文已經java8系列的第三篇了。本篇文章比較長,但我但願你們都能認真讀完。讀不完能夠先收藏,在找時間讀。沒看過前兩篇的能夠點上邊的連接看看,前兩篇文章算是對是用Stream鋪墊的一點基礎吧,不過不看也能夠學會使用Stream,但看了會有助於更好的理解和使用。在沒有深刻了解以前,我覺得Stream也是數據的載體,但後來發現並非。那麼它究竟是什麼?聽我慢慢道來。java
Stream它並非一個容器,它只是對容器的功能進行了加強,添加了不少便利的操做,例如查找、過濾、分組、排序等一系列的操做。而且有串行、並行兩種執行模式,並行模式充分的利用了多核處理器的優點,使用fork/join框架進行了任務拆分,同時提升了執行速度。簡而言之,Stream就是提供了一種高效且易於使用的處理數據的方式。git
例如:
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(2);
Stream<Integer> stream = integerList.stream();
Stream<Integer> stream1 = integerList.parallelStream();
複製代碼
例如:
int[] intArray = {1,2,3};
IntStream stream = Arrays.stream(intArray);
複製代碼
例如:
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8);
複製代碼
例如:
Stream.generate(Math::random).limit(5).forEach(System.out::print);
List<Integer> collect = Stream.iterate(0,i -> i + 1).limit(5).collect(Collectors.toList());
複製代碼
注意:使用無限流必定要配合limit截斷,否則會無限制建立下去。
若是Stream只有中間操做是不會執行的,當執行終端操做的時候纔會執行中間操做,這種方式稱爲延遲加載或惰性求值。多箇中間操做組成一箇中間操做鏈,只有當執行終端操做的時候纔會執行一遍中間操做鏈,具體是由於什麼咱們在後面再說明。下面看下Stream有哪些中間操做。github
<T>
distinct(): 去重,經過流所生成元素的 hashCode() 和 equals() 去除重複元素。
<T>
filter(Predicate<? super T> predicate): Predicate函數在上一篇當中咱們已經講過,它是斷言型接口,因此filter方法中是接收一個和Predicate函數對應Lambda表達式,返回一個布爾值,從流中過濾某些元素。
<T>
sorted(Comparator<? super T> comparator): 指定比較規則進行排序。
<T>
limit(long maxSize): 截斷流,使其元素不超過給定數量。若是元素的個數小於maxSize,那就獲取全部元素。
<T>
skip(long n): 跳過元素,返回一個扔掉了前 n 個元素的流。若流中元素不足 n 個,則返回一個空流。與 limit(n) 互補。
<R>
map(Function<? super T, ? extends R> mapper): 接收一個Function函數做爲參數,該函數會被應用到每一個元素上,並將其映射成一個新的元素。也就是轉換操做,map還有三個應用於具體類型方法,分別是:mapToInt,mapToLong和mapToDouble。這三個方法也比較好理解,好比mapToInt就是把原始Stream轉換成一個新的Stream,這個新生成的Stream中的元素都是int類型。這三個方法能夠免除自動裝箱/拆箱的額外消耗。
<R>
flatMap(Function<? super T, ? extends Stream<? extends R>> mapper): 接收一個Function函數做爲參數,將流中的每一個值都轉換成另外一個流,而後把全部流鏈接成一個流。flatMap也有三個應用於具體類型的方法,分別是:flatMapToInt、flatMapToLong、flatMapToDouble,其做用於map的三個衍生方法相同。
終端操做執行中間操做鏈,並返回結果。終端操做咱們就不一一介紹了,只介紹一下經常使用的操做。詳細可看java.util.stream.Stream接口中的方法。算法
users.stream().forEach(user -> System.out.println(user.getName()));
複製代碼
List<User> users = Lists.newArrayList();
users.add(new User(15, "A", ImmutableList.of("1元", "5元")));
users.add(new User(25, "B", ImmutableList.of("10元", "50元")));
users.add(new User(21, "C", ImmutableList.of("100元")));
//收集名稱到List
List<String> nameList = users.stream().map(User::getName).collect(Collectors.toList());
//收集名稱到List
Set<String> nameSet = users.stream().map(User::getName).collect(Collectors.toSet());
//收集到map,名字做爲key,user對象做爲value
Map<String, User> userMap = users.stream()
.collect(Collectors.toMap(User::getName, Function.identity(), (k1, k2) -> k2));
複製代碼
<T>
findFirst(); 返回當前流中的第一個元素。<T>
findAny(); 返回當前流中的任意元素。<T>
max(Comparator<? super T> comparator); 返回流中最大值。<T>
min(Comparator<? super T> comparator); 返回流中最小值。<T>
accumulator); 能夠將流中元素反覆結合起來,獲得一個值。 返回 T。這是一個歸約操做。上面咱們提到過,說Stream的並行模式使用了Fork/Join框架,這裏簡單說下Fork/Join框架是什麼?Fork/Join框架是java7中加入的一個並行任務框架,能夠將任務拆分爲多個小任務,每一個小任務執行完的結果在合併成爲一個結果。在任務的執行過程當中使用工做竊取(work-stealing)算法,減小線程之間的競爭。segmentfault
先看下總體類圖:藍色箭頭表明繼承,綠色箭頭表明實現,紅色箭頭表明內部類。 api
實際上Stream只有兩種操做,中間操做、終端操做,中間操做只是一種標記,只有終端操做纔會實際觸發執行。因此Stream流水線式的操做大體應該是用某種方式記錄中間操做,只有調用終端操做纔會將全部的中間操做疊加在一塊兒在一次迭代中所有執行。這裏只作簡單的介紹,想詳細瞭解的能夠參考下面的參考資料中的連接。每一個Stage都會將本身的操做封裝到一個Sink裏,前一個Stage只需調用後一個Stage的accept()方法便可,並不須要知道其內部是如何處理的。有了Sink對操做的包裝,Stage之間的調用問題就解決了,執行時只須要從流水線的head開始對數據源依次調用每一個Stage對應的Sink.{begin(), accept(), cancellationRequested(), end()}方法就能夠了。數組
ifeve.com/stream
www.ibm.com/developerwo…
segmentfault.com/a/119000001…
github.com/CarpenterLe…
bash
你們看后辛苦點個贊點個關注哦!後續還會後更多的博客。有興趣能夠掃碼加羣。若有錯誤,煩請指正。 app