概要
流讓你從外部迭代轉向內部迭代。這樣蜀涨,你就用不著寫下面這樣的代碼來顯式地管理數(shù)據(jù)集合的迭代(外部迭代)了:
List<Dish> vegetarianDishes = new Arraylist<>();
for(Dish d: menu){
if(d.isVegetarian()){
vegetarianDishes.add(d);
}
}
現(xiàn)在可以使用支持filter和collect操作的Stream API(內部迭代)管理對集合數(shù)據(jù)的迭代瞎嬉。你只需要將篩選行為操作參數(shù)傳遞給fileter方法就行了:
List<Dish> vegetarianDishes = menu.stream()
.filter((d)->{d.isVegetarian}) //這里的類型可以省略
.collect(toList());
這種處理數(shù)據(jù)的方式很有用,因為你讓Stream API管理如何處理數(shù)據(jù)厚柳。這樣Stream API就可以在背后進行多種優(yōu)化佑颇。此外,使用內部迭代的話草娜,Stream API可以決定并行運行你的代碼挑胸。這要是用外部迭代的話就辦不到了,因為你只能用單一的線程挨迭代宰闰。
篩選和切片
用謂詞篩選
Streams接口支持filter方法茬贵。該操作會接受一個謂詞(一個返回boolean的函數(shù))作為參數(shù),并返回一個包括所有符合謂詞的元素的流移袍。列如:
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
篩選各異的元素
流還支持一個叫做distinct的方法解藻,它會返回一個元素各異(根據(jù)流所生成元素的hashCode和equals方法實現(xiàn))的流。列如葡盗,以下代碼會篩選出列表中所有的偶數(shù)螟左,并確保沒有重復。
List<Integer> numbers = Arrays.asList(1,2,1,3,3,2,4);
numbers.stream().filter(i->i%2==0)
.distinct()
.forEach(System.out::println);
截斷流
流支持limit(n)方法觅够,該方法會返回一個不超過給定長度的流胶背。所需的長度座位參數(shù)傳遞給limit。如果留是有序的喘先,則最多會返回前n個元素钳吟。比如,你可以建立一個List窘拯,選出熱量超過300卡路里的頭三道菜:
List<Dish> dishes = menu
.stream()
.filter(d->d.getCalories()>300)
.limit(3)
.collect(toList());
可以看到红且,該方法只選出了符合謂詞的頭三個元素,然后就立即返回了結果涤姊。
請注意limit也可以用再無序流上暇番,比如源是一個Set。這種情況下思喊,limit的結果不會以任何順序排列壁酬。
跳過元素
流和支持skip(n)方法,返回一個扔掉了前n個元素的流。如果流中元素不足n個厨喂,則返回一個空流和措。下面的代碼將跳過超過300卡路里的頭兩道菜,并返回剩下的蜕煌。
List<Dish> dishes = menu.stream()
.filter(d -> d.getCalories > 300)
.skip(2)
.coolect(toList());
映射
對流中的每一個元素應用函數(shù)
流支持map方法派阱,它會接受一個函數(shù)作為參數(shù)。這個函數(shù)會被應用到每個元素上斜纪,并將其映射成一個新的元素(使用映射一詞贫母,是因為它和轉換類似,但其中的差別在于它是“創(chuàng)建一個新版本”而不是“修改”)盒刚。列如腺劣,下面代碼提取流中菜肴的名稱:
List<String> dishNames = menu.stream()
.map(s->s.getName())
.collect(toList());
流的扁平化
已經看到如何使用map方法返回列表中每個單詞的長度了窍霞。讓我們拓展一下:對于一張單詞表炫彩,如何返回一張列表蛙卤,列出里面各不相同的字符呢凿叠?列如,給定單詞表["hello","world"], 你想要返回列表["h","e","l","o","w"拦惋,"r","d"].
可能會認為很容易媚送,可以把每個單詞映射成一張字符表噩咪,然后調用distinct來過濾重復的字符吩愧。
words.stream().
map(word ->world.split(""))
.distinct()
.collect(toList());
這個方法問題在于芋酌,傳遞給map方法的Lambda為每個單詞返回了一個String。因此雁佳,map返回的流實際上是Stream<String[]>類型的脐帝。你真正想要的是用Stream<String>來表示一個字符流。
可以使用FlatMap來解決這個問題:
List<String> uniqueCharacters =
word.stream()
.map(w -> w.split(""))//將每個單詞轉為由其字母構成的數(shù)組
.flatMap(Array::stream)//將各個生成流扁平化為單個流
.distinct()
.collect(toList());
使用flatMap方法的效果是糖权,各個數(shù)組并不是分別映射成一個流堵腹,而是映射成流的內容。所有使用map(Arrays::stream)時生成的單個流都被合并起來温兼,即扁平化為一個流秸滴。下圖說明了flatMap的效果
flatMap方法讓你把一個流中的每個值都換成一個流,然后把所有的流連接起來成為一個流募判。
查找和匹配
另一個常見的數(shù)據(jù)處理套路是看看數(shù)據(jù)集中的某些元素是否匹配一個給定的屬性。Stream API通過allMatch,anyMatch,noneMatch,findFirst和findAny方法提供了這樣的工具咒唆。
檢查謂詞是否至少匹配一個元素 anyMatch
anyMatch方法可以回答“流中是否有一個元素能匹配給定的謂詞”届垫。比如,你可以用它來看看菜單里面是否有素食可以選擇
if(menu.stream().anyMatch(Dish::isVegetarian)){
System.out.println("there is any vegetarian foot");
}
anyMatch方法返回一個boolean全释,因此是一個終端操作
檢查謂詞是否匹配所有的元素 allMatch
allMatch方法的工作原理和anyMatch類似装处,但它會看看流中的元素是否都能匹配給定的謂詞。比如,你可以用它來看看菜品是否都有利于健康(卡路里小于1000卡路里):
boolean isHealthy = menu.stream().allMatch(s -> s.getCalories() < 1000);
noneMatch
和allMatch相對的是noneMatch妄迁。它可以確保流中沒有任何元素與給定的謂詞匹配寝蹈。比如你可以用noneMatch重寫前面的列子:
boolean isHealthy = menu.stream().noneMatch(s -> s.getCalories() >= 1000);
anyMatch,allMatch和noneMatch這三個操作都用到了我們所謂的短路,這就是大家熟悉的java中的&&和||運算符短路在流中的版本登淘。
查找元素 findAny
findAny方法將返回當前流中的任意元素箫老。它可以與其他流操作結合使用。比如黔州,你想找到一道素食菜肴耍鬓。你可以結合使用filter和findAny方法來實現(xiàn)這個查詢:
Optionnal<Dish> dish = menu.stream().filter(Dish:isVegetarian).findAny();
流水線將在后臺進行優(yōu)化使其只需走一遍,并在李勇短路找到結果時立即結束流妻。
Optional簡介
Optionnal<T>類(java.util.Optional)是一個容器類牲蜀,代表一個值存在或不存在。在上面的代碼中绅这。findAny可能什么元素都沒找到涣达。java8的庫設計人員引入了Optional<T>,這樣就不用返回中所周知的null了。
Optional里面幾種可以迫使你顯示的檢查值是否存在或處理值不存在的情形的方法也不錯证薇。
- isPresent() 將在Optional包含值的時候返回true峭判,否則返回false.
- ifPresent(Consumer<T> block)會在值存在的時候執(zhí)行給定的代碼塊。介紹了Consumer函數(shù)式接口棕叫;它讓你傳遞一個接收T類型的參數(shù)林螃,并返回void的lambda表達式
- T get()會在值存在的時候返回值,否則拋出一個NoSuchElement異常
- T orElse(T other) 會在值存在時返回值俺泣,否則返回一個默認值疗认。
查找第一個元素
找到第一個平法能被3整除的數(shù):
List<Integer> someNumbers = Arrays.asList(1,2,3,4,5);
Optional<Integer> firstSquareDivisibleByThree = someNumbers.stream().
map(x - x * x)
.filter(x -> x % 3 == 0)
.findFirst();
何時使用findFirst和findAny
你可能會想,為什么同時有findFirst和findAny呢伏钠?答案是==并行==横漏。找到第一個元素在并行上限制更多。如果你不關心返回的元素是哪個熟掂,請使用findAny缎浇,因為它在使用并行流時限制更少。
歸約
到目前為止赴肚,你見到過的終端操作都是返回一個boolean(allMatch之類的)素跺,void(forEach)或Optionnal對象(findAny)。也見過了使用collect來將流中的所有元素組合成一個List誉券。
本節(jié)中指厌,你將看到如何把一個流中的流中的元素組合起來,使用reduce操作來表達更復雜的查詢踊跟。
元素求和
在研究reduce方法之前踩验,先來看看如何使用for-each循環(huán)來對數(shù)字列表中的元素求和:
int sum = 0;
for (int x:numbers){
sum += x;
}
要是還能把所有的數(shù)字相乘,而不必去復制粘貼這段代碼,豈不是更好箕憾?這正是reduce操作的用武之地牡借,它對這種重復應用的模式做了抽象∠欤可以以下面這樣對流中所有的元素求和:
int sum = numbers.stream().reduce(0,(a,b)->a+b);
reduce接收兩個參數(shù):
- 一個初始值钠龙,這里是0;
- 一個BinaryOperater<T> 來將連個 元素結合起來產生一個新值扁远。
如果把所有的元素相乘也很簡單
int sum = numbers.stream().reduce(1,(a,b)->a * b);
無初始值
reduce還有一個重載的變體俊鱼,它不接受初始值,但是會返回一個Optional對象:
Optional<Integer> sum = numbers.stream().reduce((a,b) -> (a + b));
為什么它返回一個Optional<Integer>呢畅买?考慮流中沒有任何元素的情況并闲。reduce操作無法返回其和,因為它沒有初始值谷羞。這就是為什么結果被包裹在一個Optional對象里帝火,以表明和可能不存在。
最大值和最小值
reduce操作會考慮新值和流中下一個元素湃缎,并產生一個新的最大值犀填,值到整個流消耗完∩のィ可以用下面的代碼來計算最大值
int maxNumber = numbers.stream().reduce(0,(a,b) -> Interger.max(a,b));
終端操作和中間操作
數(shù)值流
原始類型流特性
java8 引入了三個原始類型特化流接口來解決這個問題:IntStream,DoubleStream和LongStream,分別將流中的元素特化為int九巡,long和double,從而避免了暗含的裝箱成本。每個接口都帶來了進行常用數(shù)值規(guī)約的新方法蹂季,比如對數(shù)值流求和的sum冕广,找到最大元素的max。==這些特化的原因并不在于流的復雜性偿洁,而是裝箱造成的復雜性——即類似int和Integer之間的效率差==異撒汉。
映射到數(shù)值流
將流轉換為特化版本的常用方法是mapToInt,mapToDouble和mapToLong。這些方法和前面說的map方法的功能做方式一樣涕滋,只是他們返回的是一個特化流睬辐,而不是Stream<T>。列如宾肺,你可以像下面這樣用mapToInt對menu中的卡路里求和:
int totalCalories = menu.stream()
.mapToInt((s) -> s.getCalories)
.sum();
這里溯饵,mapToInt會從每道菜中提取熱量(用一個Integer表示),并返回一個IntStream(而不是一個Stream<Integer>)爱榕。然后你就可以調用Integer接口中定義的sum方法瓣喊,對卡路里進行求和。==請注意黔酥,如果流是空的,sum默認返回0==.IntStream還支持其他的方便方法,如max跪者,min棵帽,average等等。
轉換回對象流
同樣渣玲,一旦有了數(shù)值流逗概,你可能會想把它轉換回非特化流。列如忘衍,IntStream上的操作只能產生原始整數(shù):IntStream的map操作接收的Lambda必須接收int并且返回int(一個IntUnaryOperator)逾苫。但是你可能想要生成另一類值,比如Dish枚钓。為此铅搓,你需要訪問Stream接口中定義的那些更廣義的操作。要把原始流轉換成一般的流(每個int都會裝箱成一個Integer),可以使用boxed方法搀捷。
IntStream intStream = menu.stream().mapToInt((s)->s.getCalories())
Stream<Integer> stream = intStream.boxed();
構建流
由值創(chuàng)建流
可以使用靜態(tài)方法Stream.of星掰,通過顯示值創(chuàng)建一個流。它可以接受任意數(shù)量的參數(shù)嫩舟。列如氢烘,一下代碼直接使用Stream.of創(chuàng)建了一個字符串流。然后你可以將字符串轉化為答謝家厌,再一個個的打印出來:
Stream<Stream> stream = Stream.of("java8","lambdas","in","action");
Stream.map((s)->s.toUpperCase).forEach((s)->System.out.println(s));
你可以使用empty得到一個空流播玖,如下所示:
Stream<String> emptyStream = Stream.empty();
由數(shù)組創(chuàng)建流
可以使用靜態(tài)方法Arrays.stream從數(shù)組創(chuàng)建一個流。它接受一個數(shù)組作為參數(shù)饭于。例如蜀踏,可以講一個原始類型int的數(shù)組轉換成一個IntStream。
int[] numbers ={1,2,3,4,5,6,7}
int sum = Arrays.stream(numbers).sum();
由文件生成流
java中用于處理文件等I/O操作的NIO API(非阻塞I/O)已經更新镰绎,以便利用Sream API
java.nio.file.Files中的很多靜態(tài)方法都會返回一個流脓斩。例如,一個很有的方法是File.lines,它會返回一個由指定文件中的各行構成的字符串流畴栖∷婢玻可以yoga這個方法看看一個文件中有多少各不相同的詞:
long uniqueWords = 0;
try{
Stream<String> lines = Files.lines(Paths.get("data.txt"),Charset.defaultCharset());
uniqueWords = lines.flatMap(line ->Arrays.stream(line.split(""))).distinct().count();
}catch(IOExcepiton e){
}
由函數(shù)生成流:創(chuàng)建無限流
Stream API提供了兩個靜態(tài)方法來從函數(shù)生成流:Stream.iterate和Stream.generate。這兩個操作可以創(chuàng)建所謂的無限流:不像從固定集合創(chuàng)建的流的那樣有固定大小的流吗讶。由iterate和generate產生的流會用給定的函數(shù)按需創(chuàng)建值燎猛,因此可以無窮地計算下去!一般來說照皆,應該使用limit(n)來對這種流加以限制重绷,以避免打印無窮多個值。
迭代 iterate
先看一個簡單的iterate的列子膜毁,然后再解釋:
Stream.iterate(0,n -> n + 2)
.limit(10)
.forEach(System.out::println);
這個流將會無限的打印昭卓,2愤钾,4,候醒,6能颁,8,10.此操作將生成一個無限流——這個流沒有結尾倒淫,因為值是按需計算的伙菊,可以永遠計算下去。
斐波那契序列
Stream.iterate(new int[]{0,1},t->new int[]{t[1],t[0]+t[1])
.limit(10)
.map(t->t[0])
.forEach(System.out::println);
生成 generate
與iterate方法類似敌土,generate方法也可以讓你按需生成一個無限流镜硕。但是generate不是依次對每個新生成的值應用函數(shù)的。它接受一個Supplier<T>類型的Lambda提供新的值返干。
Stream.generate(Math::random) //不會對生成的值計算兴枯。
.limit(5)
.forEach(System.out::println);