若是說前面幾章是函數式編程的方法論,那麼 Stream 流就應該是 JAVA8 爲咱們提供的最佳實踐。java
Stream 流的定義編程
Stream 是支持串行和並行操做的一系列元素。流操做會被組合到流管道中(Pipeline)中,一個流管道必須包含一個源(Source),這個源能夠是一個數組(Array),集合(Collection)或者 I/O Channel,會有一個或者多箇中間操做,中間操做的意思就是流與流的操做,流還會包含一個停止操做,這個停止操做會生成一個結果。設計模式
Stream 流的做用數組
以函數式編程的方式更好的操做集合。徹底依賴於函數式接口。在 java.util.stream 包中。app
流的建立方式dom
使用數組的方式ide
//第一種方式,使用 Stream.of 方法 Stream stream1 = Stream.of("hello","world","hello world"); String[] myArray = new String[]{"hello","world","hello world"}; Stream stream2 = Stream.of(myArray); //第二種方式,使用 Arrays.stream() Stream stream3 = Arrays.stream(myArray);
使用集合的方式函數式編程
Stream 的做用是以函數式編程的方式操做集合,因此對於集合類,必定有更好更方便的方法去建立 Stream 流。函數
List<String> list = Arrays.asList(myArray); Stream stream4 = list.stream();
對於集合類,直接調用 stream 方法就能夠得到這個集合對應的 Stream 流。經過查看源碼咱們發現這個方法直接定義在 Collection 接口中,而且是一個默認方法。因此全部 Collection 的子類均可以直接調用這個方法。這也是最爲經常使用的方法。性能
default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); }
使用文件流(基本不會使用,簡單瞭解便可)
下面是一個直接讀取文件中的內容而且轉化爲 Stream 流,最後輸出的過程。
//文件流 private static Stream<String> fileStream(){ Path path = Paths.get("C:\\Users\\abs\\a.txt"); try(Stream<String> lines = Files.lines(path)){ lines.forEach(System.out::println); return lines; }catch(IOException e){ throw new RuntimeException(e); } }
其餘方式
最後的方式是用於建立無限流,無限流的意思是若是你不加任何限制,流中的數據是無限。用於建立無限流的方法有 iterator 和 generate。
generate 方法須要傳入一個 Supplier 類型的函數式接口,這個函數式接口用於產生無限流中所須要的數據。
//全是數字 1 的無限流 Stream.generate(()->1); //隨機數字的無限流 Stream.generate(Math::random);
iterator 方法須要傳入兩個參數,第一個給定一個初始值,第二個參數是一個函數式接口 UnaryOperator,這個函數式接口就是輸入和輸出相同的 Function 接口。
Stream.iterate(0,n->n+1).limit(10).forEach(System.out::println);
輸出結果:
0 1 2 3 4 5 6 7 8 9
上面的 limit 是避免無限流一直產生,到達指定個數就中止。
Steam 流的優點
下面咱們經過一個簡單的例子來了解一下使用 Stream 流到底有哪些好處。等咱們學完 Stream API 後會給你們提供更多的例子,讓你們真正瞭解它。
好比咱們給定一個 List 集合,裏面放了不少數字,咱們想要獲得數字的平方而後求和。
之前的寫法:
List<Integer> l = Arrays.asList(1,2,3,4,5,6,7); int res = 0; for(int i=0;i<l.size();i++){ res += i*i; }
使用 Stream 後的寫法只須要一行代碼:
int r = l.stream().map(i->i+i).reduce(0,Integer::sum);
你們如今可能不明白 map 或者 reduce 的做用,咱們稍後會詳細講解這一部分,這裏只是想讓你們看看區別,以及認識到 Stream 對於函數式編程的使用和好處。
Stream 流的特性和原理
流不存儲值,經過管道的方式獲取值。對流的操做會生成一個結果,不過並不會修改底層的數據源。集合能夠做爲流的底層數據源,也能夠經過 generate/iterator 方法來生成數據源。
獲得流以後,咱們能夠對流中的數據進行不少操做,好比過濾,映射,排序等等,處理完以後的數據能夠再次被收集起來轉化爲咱們須要的數據類型。
從上面的圖咱們能夠看出,一個完成的流操做過程是包含兩種類型的,一個是中間操做,一個是終止操做。中間操做指的是過濾,排序和映射等中間處理過程的方法,終止操做指的是咱們將流處理完畢後返回結果的操做,好比 collect,reduce 和 count 等等。
中間操做:一個流後面能夠跟隨零個或者多箇中間操做。其目的只要是打開流,作出某種程度的數據映射/過濾,而後返回一個新的流,交給下一個操做使用,這些操做都是延遲的,就是說僅僅調用到這些類的方法,並無真正開始流的遍歷。
終止操做:一個流只能有一個終止操做,當這個操做執行後,流就被使用光了,沒法再被操做。因此這一定是流的最後一個操做。終止操做的執行,纔會是真正開始流的遍歷,而且會生成一個結果。
還有一個重要的概念流是惰性的
,在數據源上的計算只有數據在被終止的時候纔會被執行。也就是說全部的中間操做都是惰性求值,不遇到終止操做,中間操做的代碼是不會執行的。
舉個例子:
咱們對於一個數字結合進行一個 map 中間操做,將元素乘以 2,同時咱們有一個 System.out 語句用於查看代碼是否執行了。
List<Integer> l = Arrays.asList(1,2,3,4,5,6,7); l.stream().map(i->{ i = i*2; System.out.println(i); return i; });
最終的執行結果是什麼也沒打印。
那若是咱們給他加一個終止操做那,結果以下:
中間操做:2 終止操做:2 中間操做:4 終止操做:4 中間操做:6 終止操做:6 中間操做:8 終止操做:8 中間操做:10 終止操做:10 中間操做:12 終止操做:12 中間操做:14 終止操做:14
這時中間操做和終止操做都執行了,這證實中間操做是惰性的。
還有一個須要注意的點就是,Stream 其實與 IO Stream 的概念是一致的,是不能重複使用的,關閉(執行終止操做後就關閉了)後也是不能使用的。
//用集合生成一個流並進行過濾,過濾後返回一個 stream s1。 List<Integer> l = Arrays.asList(1, 2, 3, 4, 5, 6, 7); Stream s1 = l.stream().filter(item -> item > 2); //由於 filter 是中間操做,流並無被關閉,因此還能夠執行其餘操做,distnict 是一個終止操做,執行完畢後流就關閉了 s1.distinct(); //流已經關閉了,再執行操做就會拋出異常 s1.forEach(System.out::println);
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229) at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) at com.paul.framework.chapter7.StreamCreate.main(StreamCreate.java:48)
Stream 流的 API 採用了建造者設計模式,這就意味着咱們能夠在一句代碼中連續調用 Stream 的 API。
中間操做 API
filter
顧名思義,filter 就是過濾的意思。參數須要咱們傳入一個 Predicate 類型的函數式接口。不符合 Predicate 函數式接口的條件的流將被過濾出去。
Stream<T> filter(Predicate<? super T> predicate);
咱們須要篩選出分數大於 60 分的學生:
public static void main(String[] args) { List<Student> lists = new ArrayList<>(); lists.add(new Student("wang",80,"Female")); lists.add(new Student("li",95,"Male")); lists.add(new Student("zhao",100,"FeMale")); lists.add(new Student("qian",54,"Male")); // filter 是一箇中間操做,返回過濾後的 Stream 流。forEach 是一個終止操做,對 filter 過濾以後的流進行處理。 lists.stream().filter(s->s.getMark()>60).forEach((s)-> System.out.println(s.getName())); } //上一個例子咱們對過濾後的流進行了打印操做,咱們其實也能夠把過濾後的流整理成一個集合 List<Student> l = lists.stream().filter(s->s.getMark()>60).collect(Collectors.toList()); l.forEach(s-> System.out.println(s.getName()));
兩次打印的結果是相同的:
//第一次打印的結果 wang li zhao //第二次打印的結果 wang li zhao
filter 函數爲咱們提供了最爲簡單的方法去過濾集合,避免了重複代碼,邏輯也更易懂。
map
經過流的方式對集合中的元素進行匹配操做。參數須要咱們傳入一個 Function 類型的函數式接口。Function 類型的函數式接口須要一個輸入和一個輸出,對應映射以前須要的元素和映射以後獲得的元素。
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
好比集合中的元素是學生類,咱們最終想要獲得的結果是學生的分數,就可使用 map 方法。
List<Student> lists = new ArrayList<>(); lists.add(new Student("wang",80,"Female")); lists.add(new Student("li",95,"Male")); lists.add(new Student("zhao",100,"FeMale")); lists.add(new Student("qian",54,"Male")); lists.stream().map(s->s.getMark()).collect(Collectors.toList()).forEach(System.out::println);
將 list 轉換爲 stream 後,經過 map 方法將學生類轉換成學生成績的 int 類型,而後經過 collect 方法將流轉換爲集合,最後經過 forEach 方法將學生成績打印出來。
打印的結果:
80 95 100 54
好比咱們想將集合中的字符串轉換成大寫字母。
List<String> list = Arrays.asList("hello","world","helloworld","test"); list.stream().map(String::toUpperCase).collect(Collectors.toList()).forEach(System.out::println);
map 方法裏咱們經過方法引用(將字符串轉爲大寫的方法 String 類已經定義好了,因此咱們直接使用方法引用,而不是寫一個匿名函數的 Lambda 表達式)將集合中的字符串轉換成大寫,而後經過 collect 將流轉換爲集合,最終使用 forEach 方法打印轉換後的字符串。
打印結果:
HELLO WORLD HELLOWORLD TEST
mapTo*
若是咱們的 map 方法返回值是 int,long 或者 double 的話,咱們能夠直接使用 Stream API 爲咱們提供了 mapToInt,mapToLong,mapToDouble 方法。這幾個方法返回的是 IntStream,LongStream 和 DoubleStream。
mapToInt, mapToLong 和 mapToDouble 是爲了不自動拆裝箱帶來的性能損耗。你們應該知道,像 int,long,double 這種基本數據類型是不能使用面向對象相關操做的,爲此 Java 引入了自動拆裝箱的功能,可以在須要使用面向對象的特性時幫咱們將基本數據類型 int,long 和 double 轉換爲 Integer,Long 和 Double 等包裝類型。在須要使用基本數據類型時(好比計算),又能夠將包裝類型 Integer,Long 和 Double 轉換爲基本數據類型 int,long 和 double。
若是咱們使用的不對,就會有一些自動拆裝箱的性能損耗。
若是咱們須要獲得基本數據類型的結果,就可使用 mapToInt, mapToLong 和 mapToDouble,這樣的到的是基本數據類型的流,能夠方便咱們進行計算等等操做。
int sum = lists.stream().mapToInt(s->s.getMark()).sum(); System.out.println(sum);
flatMap
flat 的意思是扁平化,這個函數式的做用是將咱們 map 以後的集合或者數組等等元素打散成咱們想要的元素。
flatMap 方法須要返回一個 Stream 數據類型。T 是輸入的集合類型元素,R 是打散以後的元素類型,是 Stream 類型。
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
來看一個例子:
好比咱們的流中之前有三個 ArrayList,map 以後依而後會有三個 ArrayList,flatMap 會將三個 ArrayList 合併到一個 ArrayList 中。
Stream<List<Integer>> stream = Stream.of(Arrays.asList(1),ArrayList.asList(2,3), ArrayList.asList(4,5,6)); // 將 stream 裏面的每個 list 再次轉化爲 stream<Integer>,而後在進行 map 操做。 stream.flatMap(theList->theList.stream()).map(item->item*item).forEach(System.out::println);
這個例子中,List
在看另一個例子,字符串去重複。
List<String> list = Arrays.asList("hello welcome","world hello","hello world hello","hello welcome"); //錯誤的寫法, 這是對 String[] 的 distinct List<String[]> result = list.stream().map(item->item.split(" ")).distinct().collect(Collectors.toList());
split 方法輸入的是字符串,返回的是一個字符串數組,因此最後返回的是 String 數組流 Stream<String[]>。
咱們使用 flatMap 將 String 數組打散成 String。
//正確的寫法,要用 flatmap 將 String[] 打散成 String List<String> result = list.stream().map(item->item.split(" ")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
flatMapTo*
flatMapTo* 也有許多具體的實現實現,和 mapTo* 用法相似,這裏就再也不贅述了。
limit
limit 方法能夠對流中須要返回的元素加以限制,由於流中元素的方法執行是嚴格按照順序進行的,limit 方法就至關於取前幾個元素。
咱們經過下面這個例子來了解 limit 和無限流。
IntStream.iterate(0, i->(i+1)%2).distinct().limit(6).forEach(System.out::println);
IntStream.iterate(0, i->(i+1)%2) 不斷產生 0,1,0,1,0,1..... 這樣的無限流,distinct 方法去除重複,limit 方法雖然限制流中只有 6 個元素,可是 distinct 方法先執行它會對無限流一致執行去復操做,因此方法永遠不會結束。這個 limit 在這裏也失去了做用。
執行結果雖然只顯示了 0,1。可是方法一直不會結束。
正確的寫法:
IntStream.iterate(0, i->(i+1)%2).limit(6).distinct().forEach(System.out::println);
先調用 limit 方法,限制流中只有 6 個元素,而後去重,結果打印 0,1。程序結束。
skip
skip 方法和 limit 方法的用法相似,能夠跳過流中的前幾個元素。
IntStream.iterate(0, i->i+1).limit(10).skip(3).forEach(System.out::println);
首先經過 iterate 和 limit 產生 10 Integer 個元素的流,經過 skip 跳過前三個。最終的結果以下:
3 4 5 6 7 8 9
sort
sort 方法有兩個實現,一個是不須要傳入參數的,另外一個是須要咱們傳入 Comparator。
//根據天然順序排序 Stream<T> sorted(); //根據 Comparator 的規則進行排序 Stream<T> sorted(Comparator<? super T> comparator);
咱們之前對集合排序時一般會使用 JDK 中 Collection 接口的 sort 方法:
List<String> names = Arrays.asList("java8","lambda","method","class"); //之前的寫法 Collections.sort(names, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o2.compareTo(o1); } });
使用 Lambda 表達式對上面的寫法進行一下改進:
Collections.sort(names,(o1,o2)-> o2.compareTo(o1));
如今咱們還可使用 Stream API 中的 sorted 方法:
names.stream().sorted().forEach(System.out::println); names.stream().sorted((o1,o2)-> o2.compareTo(o1)).forEach(System.out::println);
結果:
class java8 lambda method