1. 程式人生 > >Java8-流-使用流

Java8-流-使用流

篩選和切片

用謂詞篩選

filter方法
會接收一個謂詞(一個返回Boolean)作為引數,並返回一個包括所有符號謂詞的元素的流

例子:篩選所有的素菜

1 List<Dish> vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());

篩選各異的元素

distinct方法
他會返回一個元素各異的流,實現原理是根據元素的hashCode和equals方法

例子:篩選偶數,且不重複

1234 List<Integer> numbers = Arrays.asList(1
,2,1,3,3,2,4);number.stream().filter(i->1%2==0) .distinct() .forEach(System.out.println);

截斷流

limit(n)方法
該方法會返回一個不超過給定長度的流,如果流是有序的,則最多會返回前n個元素

例子:選出熱量超過300卡路里的頭三道菜

1234 List<Dish> dishes = menu.stream() .filter(d-d.getCalories()>300) .limit(3
) .collect(toList());

跳過元素

skip(n)方法,返回一個扔掉前n個元素的流,如果流中元素不足n個,則返回一個空流,請注意limit(n)和skip(n)是互補的

對映

一個常見的資料處理套路就是從某些物件中選擇資訊,比如在sql裡面,可以從表中選擇一列

對流中每個元素應用函式

map方法
它會接收一個函式作為引數,這個函式會被應用到每個元素上,並將其對映成一個新的元素(注意是建立一個新的版本,而不是去修改)

例子:提取菜餚的名稱

1 List<String> dishNames = menu.stream().map(Dish::getName).collect(toList());

流的扁平化

例子:對應一張單詞表,如果返回一個列表,列出裡面各不相同的字元
比如單詞列表[“Hello”,”Woeld”]你想要返回的列表[“H”,”e”,”l”,”o”,”W”,”r”,”d”]

你可能會覺得很容易,呼叫distinct方法就可以了

1234 words.stream() .map(word->word.split("")) .distinct() .collect(toList());

這個方法的問題在於,傳遞給map方法的lambda為每個單詞返回了一個String[],因此map返回的流實際上是Stream型別,我們真正想要的是Stream

幸好有flatMap來解決這個問題

嘗試1:使用map和Arrays.stream()
首先,你需要一個字元流,而不是陣列流,有一個Arrays.stream()的方法,可以接受一個數組併產生一個流

12345 words.stream() .map(word->word.split("")) .map(Arrays::stream) .distinct() .collect(toList());

這個方案仍然搞不定!因為現在得到的是一個流的列表,你先是把每個單詞轉換成一個字母陣列,然後把每個陣列變成一個獨立的流。

嘗試2:使用flatMap

12345 words.stream() .map(word->word.split("")) .flatMap(Arrays::stream) .distinct() .collect(toList());

flatMap方法的效果是,各個陣列並不是分別對映成一個流,而是對映成流的內容,所有使用map(Arrays::stream)時生成的單個流都被合併起來,即扁平化為一個流,

一言以蔽之,flatMap方法讓你把一個流中的每個值都換成另一個流,然後把所有的流連線起來成為一個流

查詢和匹配

檢查謂詞是否至少匹配一個元素

anyMatch方法
可以回答“流中是否有一個元素能匹配給定的謂詞”
例子:選單裡面是否有素食可選擇

123 if(menu.stream().anyMatch(Dish::isVegetarian)){ ...}

檢查謂詞是否匹配所有元素

allMatch()用法同上
與allMatch()相對的是noneMatch()

anyMatch allMatch noneMatch 三個操作都用到了我們所謂的短路,就是大家熟悉的java中的&&和||運算子短路在流中的版本

查詢元素

findAny方法
將返回當前流中的任意元素

findFirst
找到第一個元素

規約

reduce操作表達更復雜的查詢,比如”計算選單中的總卡路里”或“選單中卡路里最高的菜是哪一個” 這需要將流中的元素反覆結合起來,得到一個值,比如Integer,這樣的查詢被歸類為規約操作,用函數語言程式設計術語來說,這稱為摺疊(fold)

求和

1 int producr = numbers.stream().reduce(1,(a,b)->a*b);

reduce操作是如何作用於一個流的:
lambda反覆結合每個元素,知道流被規約為一個值

可以使用最更簡潔的程式碼:

1 int producr = numbers.stream().reduce(0,Integer::sum);

reduce還有一個過載的變體,它不接受初始值,返回一個Optional物件:
Optional sum = numbers.stream().reduce((a,b)->(a+b));

最大值和最小值

123 Optional<Integer> max = numbers.stream().reduce(Integer::max)Optional<Integer> min = numbers.stream().reduce(Integer::min)

總結下目前說到的操作

數值流

之前我們看到了可以使用reduce方法計算流中元素的總和,例如:

123 int calories = menu.stream() .map(Dish::getCalories) .reduce(0,Integer::sum);

這段程式碼的問題是,它有一個暗含的裝箱成本,每個Integer都必須拆箱成一個原始型別,再進行求和,要是可以直接像下面這樣呼叫sum方法,豈不是更好

123 int calories = menu.stream() .map(Dish::getCalories) .sum();

這是不可能的,問題在於map方法會生成一個Straem,雖然流中的元素是Integer型別,但Streams介面沒有定義sum方法,不要擔心,Stream API還提供了原始型別流特化

原始型別流特化

Java9引入了三個原始型別特化介面來解決這個問題:IntStream,DoubleStream和LongStream,分別將流中的元素特化為int,long,double,從而避免了暗含的裝箱成本,每個介面都帶了進行常用數值規約的新方法,比如對數值流求和的sum,找到最大元素的max,此外有必要時再把他們轉換回物件流的方法

1.對映到數值流
例子:

123 int calories = menu.stream() .mapToInt(Dish::getCalories) .sum();

2.轉換回物件流
把原始流轉換成一般流,可以使用boxed方法

12 IntStream intStream = menu.stream().mapToInt(Dish::getCalories);Stream<Integer> stream = intStream.boxed();

3.預設值optionalInt
求和有預設值0,但是如果計算intStream中最大的元素,就得換個法子了,因為0是錯誤的結果,我們知道Optional類,這是一個可以表示值存在或不存在的容器,Optional可以用Integer、String等參考型別來引數化,對於三種原始流特化,也分別有一個optional原始類的特化版本:OptionalInt,OptionalDouble,OptionalLong
例如:

1 OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();

數值範圍

java8引入了兩個可以用於IntStream和LongStream的靜態方法,幫助生成這種範圍:range和rangeClosed。第一個引數接受起始值,第二個引數接受結束值。
例子:

1 InStream evenNumbers = IntStream.rangClosed(1,100).filter(n->n%2==0)

構建流

本節介紹如何從值序、陣列、檔案來建立流,甚至由生成函式來建立無限流

由值建立流

可以使用靜態方法Stream.of,它可以接受任意數量的引數
例如:以下程式碼建立一個字串流,然後你可以將字串轉換為大寫,再一個個打印出來

12 Stream<String> stream = Stream.of("Java 8","Lambda","In","Action");stream.map(String::toUpperCase).forEach(System.out::println);

由陣列建立流

可以使用靜態方法Arrays.stream從陣列建立一個流,例子:

12 int[] numbers = {2,3,5,7,11,13};int sum = Arrays.stream(number).sum();

由檔案生成流

Files.lines方法,它會返回一個由指定檔案中的各行構成的字串流

建立無限流

Stream API提供兩個靜態方法來從函式生成流:Stream.iterate和Stream.generate
這兩個操作可以建立所謂的無限流:他們產生的流會用給定的函式按需建立值,因此可以無窮無盡地計算下去,一般來說,應該來說應該使用limit(n)來對這種流加以限制,以避免列印無窮多個值

例子

123 Stream.iterate(0,n->n+2) .limit(10) .forEach(System.out.println)

generate不是依次對每個生成的值應用函式的,它接受一個Supplier型別的lambda提供新的值

例子

123 Stream.generate(Math::random) .limit(5) .forEach(System.out::println)

小結

  • 流可以簡潔地表達複雜的資料處理查詢,流可以透明的並行化
  • 你可以使用filter、distinct、skip和limit對流做篩選和切片
  • 你可以使用map和flatMap提取或裝換流中的元素
  • 你可以使用findFirst和findAny方法查詢流中的元素,你可以使用allMatch、noneMatch和anyMatch方法讓流匹配給定的謂詞
  • 這些方法都利用了短路:找到結果就立即停止計算,沒有必要處理整個流
  • 你可以利用reduce方法將流中的所有元素迭代合併成一個結果,例如求和或查詢最大元素
  • filter和map等操作是無狀態的,他們並不儲存任何狀態,reduce等操作要儲存狀態才能計算出一個值,sorted和distinct等操作也要儲存狀態,因為他們需要把流中的所有元素快取起來才能返回一個新的流,這種操作稱為有狀態操作
  • 流不僅可以從集合建立,也可以從、陣列、檔案以及iterate與generate等特定方法建立
  • 無限流是沒有固定大小的流

(注:內容整理自《Java8實戰》)