Java8筆記(1)
流
流是什么
流是Java API的新成員柒巫,它允許你以聲明性方式處理數(shù)據(jù)集合(通過(guò)查詢語(yǔ)句來(lái)表達(dá)调鬓,而不
是臨時(shí)編寫(xiě)一個(gè)實(shí)現(xiàn))房匆。你可以把它們看成遍歷數(shù)據(jù)集的高級(jí)迭代器。
用到的數(shù)據(jù)例子:
Dish 類
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private final Type type;
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return name;
}
public boolean isVegetarian() {
return vegetarian;
}
public int getCalories() {
return calories;
}
public Type getType() {
return type;
}
@Override
public String toString() {
return "Dish{" +
"name='" + name + '\'' +
", vegetarian=" + vegetarian +
", calories=" + calories +
", type=" + type +
'}';
}
public enum Type { MEAT, FISH, OTHER }
}
數(shù)據(jù)生成類:
public class Data {
public static List<Dish> create(){
List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH) );
return menu;
}
}
比較
比如你想要找出低熱量的菜肴名稱
Java 7 :
public class M1 {
public static void main(String[] args) {
List<Food> foods = CreateFoodData.create();
List<Food> lowCaloricfoods = new ArrayList<>();
for(Food food : foods){
if (food.getCalory() < 400){
lowCaloricfoods.add(food);
}
}
// 用匿名類對(duì)
//菜肴排序
Collections.sort(lowCaloricfoods, new Comparator<Food>() {
@Override
public int compare(Food o1, Food o2) {
return Integer.compare(o1.getCalory(),o2.getCalory());
}
});
System.out.println(lowCaloricfoods);
// 用了一個(gè)“垃圾變量” lowCaloricfoods 叛拷。它唯一的作用就是作為一次
//性的中間容器
}
}
Java 8 :
public class M2 {
public static void main(String[] args) {
List<Food> foods = CreateFoodData.create();
List<String> lowCaloricfoodName =
foods.stream()
.filter(d -> d.getCalory() < 400)
.sorted(Comparator.comparing(Food::getCalory))
.map(Food::getName)
.collect(Collectors.toList());
System.out.println(lowCaloricfoodName);
}
}
新的方法有幾個(gè)顯而易見(jiàn)的好處:
代碼是以聲明性方式寫(xiě)的:說(shuō)明想要完成什么(篩選熱量低的菜肴)而不是說(shuō)明如何實(shí)現(xiàn)一個(gè)操作(利用循環(huán)和 if 條件等控制流語(yǔ)句)。你在前面的章節(jié)中也看到了岂却,這種方法加上行為參數(shù)化讓你可以輕松應(yīng)對(duì)變化的需求:你很容易再創(chuàng)建一個(gè)代碼版本忿薇,利用Lambda表達(dá)式來(lái)篩選高卡路里的菜肴,而用不著去復(fù)制粘貼代碼
你可以把幾個(gè)基礎(chǔ)操作鏈接起來(lái)躏哩,來(lái)表達(dá)復(fù)雜的數(shù)據(jù)處理流水線(在 filter 后面接上
sorted 署浩、 map 和 collect 操作),同時(shí)保持代碼清晰可讀扫尺。 filter 的結(jié)果被傳給了 sorted 方法筋栋,再傳給 map 方法,最后傳給 collect 方法正驻。
因?yàn)?filter 弊攘、 sorted 抢腐、 map 和 collect 等操作是與具體線程模型無(wú)關(guān)的高層次構(gòu)件,所以它們的內(nèi)部實(shí)現(xiàn)可以是單線程的襟交,也可能透明地充分利用你的多核架構(gòu)
深入
Java 8中的集合支持一個(gè)新的stream 方法迈倍,它會(huì)返回一個(gè)流(接口定義在 java.util.stream.Stream 里)
流到底是什么呢?簡(jiǎn)短的定義就是“從支持?jǐn)?shù)據(jù)處理操作的源生成的元素序列”
元素序列——就像集合一樣捣域,流也提供了一個(gè)接口啼染,可以訪問(wèn)特定元素類型的一組有序
值。因?yàn)榧鲜菙?shù)據(jù)結(jié)構(gòu)焕梅,所以它的主要目的是以特定的時(shí)間/空間復(fù)雜度存儲(chǔ)和訪問(wèn)元
素(如 ArrayList 與 LinkedList )迹鹅。但流的目的在于表達(dá)計(jì)算,比如你前面見(jiàn)到的
filter 贞言、 sorted 和 map 斜棚。集合講的是數(shù)據(jù),流講的是計(jì)算源——流會(huì)使用一個(gè)提供數(shù)據(jù)的源蜗字,如集合打肝、數(shù)組或輸入/輸出資源。 請(qǐng)注意挪捕,從有序集
合生成流時(shí)會(huì)保留原有的順序粗梭。由列表生成的流,其元素順序與列表一致级零。
- 數(shù)據(jù)處理操作——流的數(shù)據(jù)處理功能支持類似于數(shù)據(jù)庫(kù)的操作断医,以及函數(shù)式編程語(yǔ)言中
的常用操作,如 filter 奏纪、 map 鉴嗤、 reduce 、 find 序调、 match 醉锅、 sort 等。流操作可以順序執(zhí)行发绢,也可并行執(zhí)行硬耍。
流操作有兩個(gè)重要的特點(diǎn)
- 流水線——很多流操作本身會(huì)返回一個(gè)流,這樣多個(gè)操作就可以鏈接起來(lái)边酒,形成一個(gè)大
的流水線经柴。這讓我們下一章中的一些優(yōu)化成為可能,如延遲和短路墩朦。流水線的操作可以
看作對(duì)數(shù)據(jù)源進(jìn)行數(shù)據(jù)庫(kù)式查詢坯认。
- 內(nèi)部迭代——與使用迭代器顯式迭代的集合不同,流的迭代操作是在背后進(jìn)行
具體例子:
public class M1 {
public static void main(String[] args) {
List<Dish> menus = Data.create();
List<String> threeHighCaloricDishNames =
menus.stream()
.filter(dish -> dish.getCalories() > 300)
.map(Dish::getName)
.limit(3)
.collect(Collectors.toList());
System.out.println(threeHighCaloricDishNames);
}
}
在本例中,我們先是對(duì) menu 調(diào)用 stream 方法牛哺,由菜單得到一個(gè)流陋气。數(shù)據(jù)源是菜肴列表(菜單),它給流提供一個(gè)元素序列荆隘。接下來(lái)恩伺,對(duì)流應(yīng)用一系列數(shù)據(jù)處理操作: filter 、 map 椰拒、 limit和 collect
除了 collect 之外晶渠,所有這些操作都會(huì)返回另一個(gè)流,這樣它們就可以接成一條流水線燃观,于是就可以看作對(duì)源的一個(gè)查詢褒脯。
最后, collect 操作開(kāi)始處理流水線缆毁,并返回結(jié)果(它和別的操作不一樣番川,因?yàn)樗祷氐牟皇橇鳎谶@里是一個(gè) List )脊框。在調(diào)用 collect 之前颁督,沒(méi)有任何結(jié)果產(chǎn)生,實(shí)際上根本就沒(méi)有從 menu 里選擇元素浇雹。你可以這么理解:鏈中的方法調(diào)用都在排隊(duì)等待沉御,直到調(diào)用 collect
操作簡(jiǎn)介:
- filter ——接受Lambda,從流中排除某些元素昭灵。在本例中吠裆,通過(guò)傳遞lambda d ->
d.getCalories() > 300 ,選擇出熱量超過(guò)300卡路里的菜肴
map ——接受一個(gè)Lambda烂完,將元素轉(zhuǎn)換成其他形式或提取信息试疙。在本例中,通過(guò)傳遞方
法引用 Dish::getName 抠蚣,相當(dāng)于Lambda d -> d.getName() 祝旷,提取了每道菜的菜名limit ——截?cái)嗔鳎蛊湓夭怀^(guò)給定數(shù)量
- collect ——將流轉(zhuǎn)換為其他形式嘶窄。在本例中缓屠,流被轉(zhuǎn)換為一個(gè)列表。
這段代碼护侮,與逐項(xiàng)處理菜單列表的代碼有很大不同。首先储耐,我們使用了聲明性的方式來(lái)處理菜單數(shù)據(jù)羊初,即你說(shuō)的對(duì)這些數(shù)據(jù)需要做什么:“查找熱量最高的三道菜的菜名。”你并沒(méi)有去實(shí)現(xiàn)篩選( filter )长赞、提然拊堋( map )或截?cái)啵?limit )功能,Streams庫(kù)已經(jīng)自帶了得哆。因此脯颜,Stream API在決定如何優(yōu)化這條流水線時(shí)更為靈活。例如贩据,篩選栋操、提取和截?cái)嗖僮骺梢砸淮芜M(jìn)行,并在找到這三道菜后立即停止饱亮。
流與集合
比如說(shuō)存在DVD里的電影矾芙,這就是一個(gè)集合(也許是字節(jié),也許是幀近上,這個(gè)無(wú)所謂)剔宪,因?yàn)樗苏麄€(gè)數(shù)據(jù)結(jié)構(gòu)。現(xiàn)在再來(lái)想想在互聯(lián)網(wǎng)上通過(guò)視頻流看同樣的電影∫嘉蓿現(xiàn)在這是一個(gè)流(字節(jié)流或幀流)葱绒。流媒體視頻播放器只要提前下載用戶觀看位置的那幾幀就可以了,這樣不用等到流中大部分值計(jì)算出來(lái)斗锭,你就可以顯示流的開(kāi)始部分了(想想觀看直播足球賽)地淀。特別要注意,視頻播放器可能沒(méi)有將整個(gè)流作為集合拒迅,保存所需要的內(nèi)存緩沖區(qū)——而且要是非得等到最后一幀出現(xiàn)才能開(kāi)始看骚秦,那等待的時(shí)間就太長(zhǎng)了。出于實(shí)現(xiàn)的考慮璧微,你也可以讓視頻播放器把流的一部分緩存在集合里作箍,但和概念上的差異不是一回事。
粗略地說(shuō)前硫,集合與流之間的差異就在于什么時(shí)候進(jìn)行計(jì)算胞得。集合是一個(gè)內(nèi)存中的數(shù)據(jù)結(jié)構(gòu),它包含數(shù)據(jù)結(jié)構(gòu)中目前所有的值——集合中的每個(gè)元素都得先算出來(lái)才能添加到集合中屹电。(你可以往集合里加?xùn)|西或者刪東西阶剑,但是不管什么時(shí)候,集合中的每個(gè)元素都是放在內(nèi)存里的危号,元素都得先算出來(lái)才能成為集合的一部分牧愁。)
相比之下,流則是在概念上固定的數(shù)據(jù)結(jié)構(gòu)(你不能添加或刪除元素)外莲,其元素則是按需計(jì)算的猪半。 這對(duì)編程有很大的好處
只能遍歷一次
public class M1 {
public static void main(String[] args) {
List<String> title = Arrays.asList(
"Java8", "In", "Action"
);
Stream<String> stringStream = title.stream();
stringStream.forEach(System.out::println);
// Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
stringStream.forEach(System.out::println);
}
}
和迭代器類似兔朦,流只能遍歷一次。遍歷完之后磨确,我們就說(shuō)這個(gè)流已經(jīng)被消費(fèi)掉了沽甥。你可以從原始數(shù)據(jù)源那里再獲得一個(gè)新的流來(lái)重新遍歷一遍,就像迭代器一樣(這里假設(shè)它是集合之類的可重復(fù)的源乏奥,如果是I/O通道就沒(méi)戲了)
外部迭代與內(nèi)部迭代
使用 Collection 接口需要用戶去做迭代(比如用 for-each )摆舟,這稱為外部迭代。 相反邓了,Streams庫(kù)使用內(nèi)部迭代——它幫你把迭代做了恨诱,還把得到的流值存在了某個(gè)地方,你只要給出一個(gè)函數(shù)說(shuō)要干什么就可以了
流操作
例子:
List<String> names = menu.stream()
.filter(d -> d.getCalories() > 300)
.map(Dish::getName)
.limit(3)
.collect(toList());
兩類操作:
filter 驶悟、 map 和 limit 可以連成一條流水線
collect 觸發(fā)流水線執(zhí)行并關(guān)閉它
可以連接起來(lái)的流操作稱為中間操作胡野,關(guān)閉流的操作稱為終端操作
中間操作
諸如 filter 或 sorted 等中間操作會(huì)返回另一個(gè)流。這讓多個(gè)操作可以連接起來(lái)形成一個(gè)查詢痕鳍。重要的是硫豆,除非流水線上觸發(fā)一個(gè)終端操作,否則中間操作不會(huì)執(zhí)行任何處理——它們很懶笼呆。這是因?yàn)橹虚g操作一般都可以合并起來(lái)熊响,在終端操作時(shí)一次性全部處理。
為了搞清楚流水線中到底發(fā)生了什么诗赌,我們把代碼改一改:
public class M1 {
public static void main(String[] args) {
List<Dish> menu = Data.create();
List<String> names =
menu.stream()
.filter(dish ->
{
System.out.println("filter " + dish.getName());
return dish.getCalories() > 300;
})
.map(dish ->
{
System.out.println("map " + dish.getName());
return dish.getName();
})
.limit(3)
.collect(Collectors.toList());
System.out.println("=====================================");
System.out.println(names);
}
}
輸出:
filtering pork
mapping pork
filtering beef
mapping beef
filtering chicken
mapping chicken
[pork, beef, chicken]
你會(huì)發(fā)現(xiàn)汗茄,有好幾種優(yōu)化利用了流的延遲性質(zhì):
第一,盡管很多菜的熱量都高于300卡路里铭若,但只選出了前三個(gè)洪碳!這是因?yàn)?limit 操作和一種稱為短路的技巧
第二,盡管 filter 和 map 是兩個(gè)獨(dú)立的操作叼屠,但它們合并到同一次遍歷中了(我們把這種技術(shù)叫作循環(huán)合并)
終端操作
終端操作會(huì)從流的流水線生成結(jié)果瞳腌。其結(jié)果是任何不是流的值,比如 List 镜雨、 Integer ,甚
至void
在下面的流水線中嫂侍, forEach 是一個(gè)返回 void 的終端操作,它會(huì)對(duì)源中的每道菜應(yīng)用一個(gè)Lambda荚坞。把 System.out.println 傳遞給 forEach 挑宠,并要求它打印出由 menu 生成的流中的每一個(gè) Dish :
public class M1 {
public static void main(String[] args) {
List<Dish> menu = Data.create();
menu.stream().forEach(System.out::println);
}
}
使用流
流的使用一般包括三件事:
一個(gè)數(shù)據(jù)源(如集合)來(lái)執(zhí)行一個(gè)查詢
一個(gè)中間操作鏈,形成一條流的流水線
一個(gè)終端操作颓影,執(zhí)行流水線各淀,并能生成結(jié)果
篩選和切片
用謂詞篩選
Streams 接口支持 filter 方法。該操作會(huì)接受一個(gè)謂詞(一個(gè)返回boolean 的函數(shù))作為參數(shù)诡挂,并返回一個(gè)包括所有符合謂詞的元素的流揪阿。
例子:
public class M1 {
public static void main(String[] args) {
//篩選出所有素菜疗我,創(chuàng)建一張素食菜單:
List<Dish> menu = Data.create();
List<Dish> vegetarianMenu = menu.stream()
.filter(Dish::isVegetarian)
.collect(toList());
}
}
篩選各異的元素
流還支持一個(gè)叫作 distinct 的方法,它會(huì)返回一個(gè)元素各異(根據(jù)流所生成元素的hashCode 和 equals 方法實(shí)現(xiàn))的流
public class M1 {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
// 篩選出列表中所有的偶數(shù)南捂,并確保沒(méi)有
//重復(fù)
numbers.stream()
.filter( i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);
}
}
截短流
流支持 limit(n) 方法,該方法會(huì)返回一個(gè)不超過(guò)給定長(zhǎng)度的流旧找。所需的長(zhǎng)度作為參數(shù)傳遞給 limit 溺健。如果流是有序的,則最多會(huì)返回前 n 個(gè)元素钮蛛。
public class M1 {
public static void main(String[] args) {
List<Dish> menu = Data.create();
// 你可以建立一個(gè) List 鞭缭,選出熱量
//超過(guò)300卡路里的頭三道菜:
List<Dish> dishes = menu.stream()
.filter(dish -> dish.getCalories() > 300)
.limit(3)
.collect(Collectors.toList());
System.out.println(dishes);
}
}
跳過(guò)元素
流還支持 skip(n) 方法岭辣,返回一個(gè)扔掉了前 n 個(gè)元素的流甸饱。如果流中元素不足 n 個(gè),則返回一個(gè)空流偷遗。請(qǐng)注意驼壶, limit(n) 和 skip(n) 是互補(bǔ)的!
public class M1 {
public static void main(String[] args) {
List<Dish> menu = Data.create();
List<Dish> dishes = menu.stream()
.filter(dish -> dish.getCalories() > 500)
.skip(2)
.collect(Collectors.toList());
System.out.println(dishes);
}
如何利用流來(lái)篩選前兩個(gè)葷菜
public class Test_5_1 {
public static void main(String[] args) {
List<Dish> menu = Data.create();
List<Dish> dishes = menu.stream()
.filter(dish -> dish.getType() == Dish.Type.MEAT)
.collect(Collectors.toList());
System.out.println(dishes);
}
}
映射
一個(gè)非常常見(jiàn)的數(shù)據(jù)處理套路就是從某些對(duì)象中選擇信息泵喘。比如在SQL里,你可以從表中選擇一列般妙。Stream API也通過(guò) map 和 flatMap 方法提供了類似的工具纪铺。
對(duì)流中每一個(gè)元素應(yīng)用函數(shù)
流支持 map 方法,它會(huì)接受一個(gè)函數(shù)作為參數(shù)股冗。這個(gè)函數(shù)會(huì)被應(yīng)用到每個(gè)元素上霹陡,并將其映射成一個(gè)新的元素(使用映射一詞,是因?yàn)樗娃D(zhuǎn)換類似止状,但其中的細(xì)微差別在于它是“創(chuàng)建一個(gè)新版本”而不是去“修改”)
例如:
public class M1 {
// 下面的代碼把方法引用 Dish::getName 傳給了 map 方法烹棉,
//來(lái)提取流中菜肴的名稱:
public static void main(String[] args) {
List<Dish> menu = Data.create();
List<String> names = menu.stream()
.map(Dish::getName)
.collect(Collectors.toList());
System.out.println(names);
}
}
因?yàn)?getName 方法返回一個(gè) String ,所以 map 方法輸出的流的類型就是Stream<String> 怯疤。
給定一個(gè)單詞列表浆洗,你想要返回另一個(gè)列表,顯示每個(gè)單詞中有幾個(gè)字母集峦。怎么做呢伏社?你需要對(duì)列表中的每個(gè)元素應(yīng)用一個(gè)函數(shù)抠刺。這聽(tīng)起來(lái)正好該用 map 方法去做!應(yīng)用的函數(shù)應(yīng)該接受一個(gè)單詞摘昌,并返回其長(zhǎng)度
public class M2 {
public static void main(String[] args) {
List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(toList());
System.out.println(wordLengths);
}
}
提取菜名的例子速妖。如果你要找出每道菜的名稱有多長(zhǎng),怎么做
public class M3 {
public static void main(String[] args) {
List<Dish> menu = Data.create();
List<Integer> names = menu.stream()
.map(Dish::getName)
.map(String::length)
.collect(Collectors.toList());
System.out.println(names);
}
流的扁平化
對(duì)于一張單詞 表 聪黎, 如 何 返 回 一 張 列 表 , 列 出 里 面 各 不 相 同 的 字 符 呢 锦秒? 例 如 旅择, 給 定 單 詞 列 表["Hello","World"] ,你想要返回列表 ["H","e","l", "o","W","r","d"] 铺敌。
版本一:
public class M1 {
public static void main(String[] args) {
String[] Words = {"Goodbye", "World","kdffj"};
Arrays.stream(Words)
.map(word -> word.split(""))
.distinct()
.collect(Collectors.toList());
}
}
這個(gè)方法的問(wèn)題在于产弹,傳遞給 map 方法的Lambda為每個(gè)單詞返回了一個(gè) String[] ( String列表)痰哨。因此斤斧, map 返回的流實(shí)際上是 Stream<String[]> 類型的。你真正想要的是用Stream<String> 來(lái)表示一個(gè)字符流
可以用 flatMap 來(lái)解決這個(gè)問(wèn)題
嘗試使用 map 和 Arrays.stream()
首先游昼,你需要一個(gè)字符流烘豌,而不是數(shù)組流廊佩。有一個(gè)叫作 Arrays.stream() 的方法可以接受個(gè)數(shù)組并產(chǎn)生一個(gè)流
String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);
把它用在前面的那個(gè)流水線里:
words.stream()
.map(word -> word.split(""))
.map(Arrays::stream)
.distinct()
.collect(toList());
當(dāng)前的解決方案仍然搞不定顽铸!這是因?yàn)榘掀疲悻F(xiàn)在得到的是一個(gè)流的列表(更準(zhǔn)確地說(shuō)是Stream<String> )!的確舷手,你先是把每個(gè)單詞轉(zhuǎn)換成一個(gè)字母數(shù)組男窟,然后把每個(gè)數(shù)組變成了一個(gè)獨(dú)立的流
使用 flatMap
如:
public class M2 {
public static void main(String[] args) {
String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);
List<String> uniqueCharacters =
streamOfwords
// 將每個(gè)單詞轉(zhuǎn)換為由其字母構(gòu)成的數(shù)組
.map(w -> w.split(""))
// 將各個(gè)生成流扁平化為單個(gè)流
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
System.out.println(uniqueCharacters);
}
}
使用 flatMap 方法的效果是,各個(gè)數(shù)組并不是分別映射成一個(gè)流汗捡,而是映射成流的內(nèi)容扇住。所有使用 map(Arrays::stream) 時(shí)生成的單個(gè)流都被合并起來(lái)艘蹋,即扁平化為一個(gè)流
一言以蔽之, flatmap 方法讓你把一個(gè)流中的每個(gè)值都換成另一個(gè)流屑迂,然后把所有的流連接起來(lái)成為一個(gè)流的榛。
給定一個(gè)數(shù)字列表夫晌,如何返回一個(gè)由每個(gè)數(shù)的平方構(gòu)成的列表
如給定[1, 2, 3, 4,5]晓淀,應(yīng)該返回[1, 4, 9, 16, 25]
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares =
numbers.stream()
.map( n -> n * n)
.collect(Collectors.toList());
System.out.println(squares);
}
給定兩個(gè)數(shù)字列表凶掰,如何返回所有的數(shù)對(duì)
例如前翎,給定列表[1, 2, 3]和列表[3, 4]港华,應(yīng)該返回[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]
public static void main(String[] args) {
List<Integer> numbers1 = Arrays.asList(1, 2, 3);
List<Integer> numbers2 = Arrays.asList(3, 4);
List<int[]> pairs =
numbers1.stream()
.flatMap(i -> numbers2.stream()
.map( j -> new int[]{i,j}))
.collect(Collectors.toList());
System.out.println(pairs);
}
擴(kuò)展前一個(gè)例子立宜,只返回總和能被3整除的數(shù)對(duì)
例如(2, 4)和(3, 3)是可以的
public static void main(String[] args) {
List<Integer> numbers1 = Arrays.asList(1, 2, 3);
List<Integer> numbers2 = Arrays.asList(3, 4);
List<int[]> pairs =
numbers1.stream()
.flatMap(i ->
numbers2.stream()
.filter(j -> (i + j) % 3 == 0)
.map(j -> new int[]{i, j})
)
.collect(toList());
System.out.println(pairs);
}
查找和匹配
另一個(gè)常見(jiàn)的數(shù)據(jù)處理套路是看看數(shù)據(jù)集中的某些元素是否匹配一個(gè)給定的屬性。Stream
API通過(guò) allMatch 帅戒、 anyMatch 施流、 noneMatch 瞪醋、 findFirst 和 findAny 方法提供了這樣的工具
檢查謂詞是否至少匹配一個(gè)元素
anyMatch 方法可以回答“流中是否有一個(gè)元素能匹配給定的謂詞”
用它來(lái)看看菜單里面是否有素食可選擇:
public class M1 {
public static void main(String[] args) {
List<Dish> menu = Data.create();
if (menu.stream().anyMatch(Dish::isVegetarian)){
System.out.println("isVegetarian");
}
//anyMatch 方法返回一個(gè) boolean 银受,因此是一個(gè)終端操作
}
}
檢查謂詞是否匹配所有元素
allMatch 方法的工作原理和 anyMatch 類似,但它會(huì)看看流中的元素是否都能匹配給定的謂詞
用它來(lái)看看菜品是否有利健康(即所有菜的熱量都低于1000卡路里):
public class M1 {
public static void main(String[] args) {
List<Dish> menu = Data.create();
boolean isOk = menu.stream()
.allMatch(dish -> dish.getCalories() < 1000);
System.out.println(isOk);
}
}
noneMatch
和 allMatch 相對(duì)的是 noneMatch 顶霞。它可以確保流中沒(méi)有任何元素與給定的謂詞匹配选浑。比如古徒,你可以用 noneMatch 重寫(xiě)前面的例子
boolean isHealthy = menu.stream()
.noneMatch(d -> d.getCalories() >= 1000);
anyMatch 代态、 allMatch 和 noneMatch 這三個(gè)操作都用到了我們所謂的短路蹦疑,這就是大家熟悉的Java中 && 和 || 運(yùn)算符短路在流中的版本
查找元素
findAny 方法將返回當(dāng)前流中的任意元素必尼。它可以與其他流操作結(jié)合使用
比如,你可能想找到一道素食菜肴育谬。你可以結(jié)合使用 filter 和 findAny 方法來(lái)實(shí)現(xiàn)這個(gè)查詢
public class M1 {
public static void main(String[] args) {
List<Dish> menu = Data.create();
Optional<Dish> dish = menu.stream()
.filter(Dish::isVegetarian)
.findAny();
System.out.println(dish);
}
}
查找第一個(gè)元素
有些流有一個(gè)出現(xiàn)順序(encounter order)來(lái)指定流中項(xiàng)目出現(xiàn)的邏輯順序(比如由 List 或排序好的數(shù)據(jù)列生成的流)。對(duì)于這種流咖刃,你可能想要找到第一個(gè)元素嚎杨。為此有一個(gè) findFirst方法枫浙,它的工作方式類似于 findany
給定一個(gè)數(shù)字列表,下面的代碼能找出第一個(gè)平方能被3整除的數(shù):
public class M1 {
public static void main(String[] args) {
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(); // 9
System.out.println(firstSquareDivisibleByThree);
}
}
歸約
把一個(gè)流中的元素組合起來(lái)紧帕,使用 reduce 操作來(lái)表達(dá)更復(fù)雜的查詢是嗜,比如“計(jì)算菜單中的總卡路里”或“菜單中卡路里最高的菜是哪一個(gè)”叠纷。此類查詢需要將流中所有元素反復(fù)結(jié)合起來(lái)涩嚣,得到一個(gè)值顷歌,比如一個(gè) Integer 眯漩。這樣的查詢可以被歸類為歸約操作(將流歸約成一個(gè)值)赦抖。
用函數(shù)式編程語(yǔ)言的術(shù)語(yǔ)來(lái)說(shuō)队萤,這稱為折疊(fold),因?yàn)槟憧梢詫⑦@個(gè)操作看成把一張長(zhǎng)長(zhǎng)的紙(你的流)反復(fù)折疊成一個(gè)小方塊赵辕,而這就是折疊操作的結(jié)果还惠。
元素求和
如何使用 for-each 循環(huán)來(lái)對(duì)數(shù)字列表中的元素求和:
int sum = 0;
for (int x : numbers) {
sum += x;
}
numbers 中的每個(gè)元素都用加法運(yùn)算符反復(fù)迭代來(lái)得到結(jié)果吸重。通過(guò)反復(fù)使用加法嚎幸,你把一個(gè)數(shù)字列表歸約成了一個(gè)數(shù)字。這段代碼中有兩個(gè)參數(shù):
總和變量的初始值替废,在這里是 0
將列表中所有元素結(jié)合在一起的操作诈火,在這里是 +
要是還能把所有的數(shù)字相乘,而不必去復(fù)制粘貼這段代碼冷守,豈不是很好拍摇?這正是 reduce 操作的用武之地,它對(duì)這種重復(fù)應(yīng)用的模式做了抽象
像下面這樣對(duì)流中所有的元素求和
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(
121,454,55,66
);
int sum = numbers.stream()
.reduce(0,(a,b) -> a +b );
System.out.println(sum);
}
reduce 接受兩個(gè)參數(shù):
- 一個(gè)初始值混卵,這里是0
- 一個(gè) BinaryOperator<T> 來(lái)將兩個(gè)元素結(jié)合起來(lái)產(chǎn)生一個(gè)新值淮菠,這里我們用的是
lambda (a, b) -> a + b
也很容易把所有的元素相乘,只需要將另一個(gè)Lambda: (a, b) -> a * b 傳遞給 reduce
操作就可以了
int product = numbers.stream().reduce(1, (a, b) -> a * b);
可以使用方法引用讓這段代碼更簡(jiǎn)潔澄阳。在Java 8中, Integer 類現(xiàn)在有了一個(gè)靜態(tài)的 sum方法來(lái)對(duì)兩個(gè)數(shù)求和肮塞,這恰好是我們想要的枕赵,用不著反復(fù)用Lambda寫(xiě)同一段代碼了
int sum = numbers.stream().reduce(0, Integer::sum);
最大值和最小值
利用剛剛學(xué)到的 reduce來(lái)計(jì)算流中最大或最小的元素
reduce 接受兩個(gè)參數(shù)
- 一個(gè)初始值
- 一個(gè)Lambda來(lái)把兩個(gè)流元素結(jié)合起來(lái)并產(chǎn)生一個(gè)新值
reduce 操作會(huì)考慮新值和流中下一個(gè)元素,并產(chǎn)生一個(gè)新的最大值篮昧,直到整個(gè)流消耗完
public class M1 {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(
121,454,55,66
);
Optional<Integer> max =
numbers.stream()
.reduce(Integer::max);
System.out.println(max);
}
}
要計(jì)算最小值懊昨,你需要把 Integer.min 傳給 reduce 來(lái)替換 Integer.max :
付諸實(shí)踐
使用數(shù)據(jù):
public class Transaction {
private final Trader trader;
private final int year;
private final int value;
public Transaction(Trader trader, int year, int value) {
this.trader = trader;
this.year = year;
this.value = value;
}
public Trader getTrader() {
return trader;
}
public int getYear() {
return year;
}
public int getValue() {
return value;
}
@Override
public String toString() {
return "Transaction{" +
"trader=" + trader +
", year=" + year +
", value=" + value +
'}';
}
}
public class Trader {
private final String name;
private final String city;
public Trader(String name, String city) {
this.name = name;
this.city = city;
}
public String getName() {
return name;
}
public String getCity() {
return city;
}
@Override
public String toString() {
return "Trader{" +
"name='" + name + '\'' +
", city='" + city + '\'' +
'}';
}
}
public class Data {
static Trader raoul = new Trader("Raoul", "Cambridge");
static Trader mario = new Trader("Mario","Milan");
static Trader alan = new Trader("Alan","Cambridge");
static Trader brian = new Trader("Brian","Cambridge");
public static List<Transaction> create(){
List<Transaction> transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
return transactions;
}
}
找出2011年發(fā)生的所有交易,并按交易額排序(從低到高)
public static void main(String[] args) {
List<Transaction> transactions = Data.create();
List<Transaction> transactions2011 =
transactions.stream()
.filter(transaction -> transaction.getYear() == 2011)
.sorted(Comparator.comparing(Transaction::getValue))
.collect(Collectors.toList());
System.out.println(transactions2011);
}
交易員都在哪些不同的城市工作過(guò)
public static void main(String[] args) {
List<Transaction> transactions = Data.create();
List<String> cities =
transactions.stream()
.map(transaction -> transaction.getTrader().getCity())
.distinct()
.collect(Collectors.toList());
System.out.println(cities);
// 或者
Set<String> cities_1 =
transactions.stream()
.map(transaction -> transaction.getTrader().getCity())
.collect(toSet());
System.out.println(cities_1);
}
查找所有來(lái)自于劍橋的交易員均抽,并按姓名排序
public static void main(String[] args) {
List<Transaction> transactions = Data.create();
List<Trader> traders =
transactions.stream()
.map(Transaction::getTrader)
.filter(trader -> trader.getCity().equals("Cambridge"))
.distinct()
.sorted(Comparator.comparing(Trader::getName))
.collect(Collectors.toList());
System.out.println(traders);
}
返回所有交易員的姓名字符串,按字母順序排序
public static void main(String[] args) {
List<Transaction> transactions = Data.create();
String traderStr =
transactions.stream()
.map(transaction -> transaction.getTrader().getName())
.distinct()
.sorted()
.reduce("",(n1,n2) -> n1 + n2);
System.out.println(traderStr);
// 請(qǐng)注意,此解決方案效率不高(所有字符串都被反復(fù)連接贤牛,每次迭代的時(shí)候都要建立一個(gè)新
//的 String 對(duì)象)
String traderStr_1 =
transactions.stream()
.map(transaction -> transaction.getTrader().getName())
.distinct()
.sorted()
.collect(joining());
System.out.println(traderStr_1);
}
有沒(méi)有交易員是在米蘭工作的
public static void main(String[] args) {
List<Transaction> transactions = Data.create();
boolean milanBased =
transactions.stream()
.anyMatch(transaction -> transaction.getTrader()
.getCity()
.equals("Milan"));
System.out.println(milanBased);
}
打印生活在劍橋的交易員的所有交易額
public static void main(String[] args) {
List<Transaction> transactions = Data.create();
transactions.stream()
.filter(transaction -> transaction.getTrader()
.getCity().equals("Cambridge"))
.forEach(System.out::println);
}
所有交易中,最高的交易額是多少
public static void main(String[] args) {
List<Transaction> transactions = Data.create();
Optional<Integer> highestValue =
transactions.stream()
.map(Transaction::getValue)
.reduce(Integer::max);
System.out.println(highestValue);
}
找到交易額最小的交易
public static void main(String[] args) {
List<Transaction> transactions = Data.create();
Optional<Transaction> smallestTransaction =
transactions.stream()
.reduce((t1,t2) ->
t1.getValue() < t2.getValue() ? t1 : t2);
// 流支持 min 和 max 方法武鲁,它們可以接受一個(gè) Comparator 作為參數(shù)沐鼠,指定
//計(jì)算最小或最大值時(shí)要比較哪個(gè)鍵值:
Optional<Transaction> smallestTransaction_1 =
transactions.stream()
.min(comparing(Transaction::getValue));
}
數(shù)值流
可以使用 reduce 方法計(jì)算流中元素的總和。例如憔涉,你可以像下面這樣計(jì)算菜單的熱量:
int calories = menu.stream()
.map(Dish::getCalories)
.reduce(0, Integer::sum);
這段代碼的問(wèn)題是监氢,它有一個(gè)暗含的裝箱成本浪腐。每個(gè) Integer 都必須拆箱成一個(gè)原始型议街,再進(jìn)行求和
原始類型流特化
Java 8引入了三個(gè)原始類型特化流接口來(lái)解決這個(gè)問(wèn)題: IntStream 特漩、 DoubleStream 和LongStream 雄卷,分別將流中的元素特化為 int 丁鹉、 long 和 double ,從而避免了暗含的裝箱成本
映射到數(shù)值流
將流轉(zhuǎn)換為特化版本的常用方法是 mapToInt 冯凹、 mapToDouble 和 mapToLong
這些方法和前面說(shuō)的 map 方法的工作方式一樣宇姚,只是它們返回的是一個(gè)特化流,而不是 Stream<T>
比如用 mapToInt 對(duì) menu 中的卡路里求和:
public static void main(String[] args) {
List<Dish> menu = Data.create();
int calories =
menu.stream()
.mapToInt(Dish::getCalories)
.sum();
}
這里, mapToInt 會(huì)從每道菜中提取熱量(用一個(gè) Integer 表示)啼止,并返回一個(gè) IntStream(而不是一個(gè) Stream<Integer> )。然后你就可以調(diào)用 IntStream 接口中定義的 sum 方法巩那,對(duì)卡路里求和了
如果流是空的即横, sum 默認(rèn)返回 0 桨嫁。 IntStream 還支持其他的方便方法彬坏,如max 幻赚、 min 戴涝、 average 等
轉(zhuǎn)換回對(duì)象流
同樣蓄拣,一旦有了數(shù)值流贷洲,你可能會(huì)想把它轉(zhuǎn)換回非特化流。例如恭垦, IntStream 上的操作只能產(chǎn)生原始整數(shù): IntStream 的 map 操作接受的Lambda必須接受 int 并返回 int (一個(gè)IntUnaryOperator )贴铜。但是你可能想要生成另一類值陷嘴,比如 Dish
為此,你需要訪問(wèn) Stream接口中定義的那些更廣義的操作秒拔。要把原始流轉(zhuǎn)換成一般流(每個(gè) int 都會(huì)裝箱成一個(gè)Integer )庵芭,可以使用 boxed 方法
public static void main(String[] args) {
List<Dish> menu = Data.create();
IntStream intStream = menu.stream()
// 將 Stream 轉(zhuǎn)換為數(shù)值流
.mapToInt(Dish::getCalories);
// 將數(shù)值流轉(zhuǎn)換為 Stream
Stream<Integer> stream = intStream.boxed();
System.out.println(stream.toString());
}
默認(rèn)值 OptionalInt
求和的那個(gè)例子很容易好乐,因?yàn)樗幸粋€(gè)默認(rèn)值: 0 区转。但是蜻韭,如果你要計(jì)算 IntStream 中的最大元素肖方,就得換個(gè)法子了司草,因?yàn)?0 是錯(cuò)誤的結(jié)果娩怎。如何區(qū)分沒(méi)有元素的流和最大值真的是 0 的流呢崩瓤?
前面我們介紹了 Optional 類肾扰,這是一個(gè)可以表示值存在或不存在的容器偷拔。 Optional 可以用Integer 蛤签、 String 等參考類型來(lái)參數(shù)化戳晌。對(duì)于三種原始流特化豪嚎,也分別有一個(gè)Optional 原始類型特化版本: OptionalInt 妄荔、 OptionalDouble 和 OptionalLong 篷角。
要找到 IntStream 中的最大元素嘉蕾,可以調(diào)用 max 方法以清,它會(huì)返回一個(gè) OptionalInt
public static void main(String[] args) {
List<Dish> menu = Data.create();
OptionalInt maxCalories =
menu.stream()
.mapToInt(Dish::getCalories)
.max();
System.out.println(maxCalories);
}
如果沒(méi)有最大值的話,你就可以顯式處理 OptionalInt 去定義一個(gè)默認(rèn)值了
int max = maxCalories.orElse(1);
數(shù)值范圍
假設(shè)你想要生成1和100之間的所有數(shù)字。Java 8引入了兩個(gè)可以用于 IntStream 和 LongStream 的靜態(tài)方法,幫助生成這種范圍:range 和 rangeClosed 睡蟋。這兩個(gè)方法都是第一個(gè)參數(shù)接受起始值隔缀,第二個(gè)參數(shù)接受結(jié)束值牵触。但range 是不包含結(jié)束值的绰更,而rangeClosed 則包含結(jié)束值
public static void main(String[] args) {
IntStream evenNumbers = IntStream.rangeClosed(1,100)
.filter(n->n % 2 == 0);
// 從1到100有50個(gè)偶數(shù)
System.out.println(evenNumbers.count());
}
數(shù)值流應(yīng)用:勾股數(shù)
創(chuàng)建一個(gè)勾股數(shù)流
表示三元數(shù)
第一步是定義一個(gè)三元數(shù)。雖然更恰當(dāng)?shù)淖龇ㄊ嵌x一個(gè)新的類來(lái)表示三元數(shù)爹袁,但這里你可以使用具有三個(gè)元素的 int 數(shù)組,比如 new int[]{3, 4, 5} 见芹,來(lái)表示勾股數(shù)(3, 4, 5)《郏現(xiàn)在你就可以用數(shù)組索引訪問(wèn)每個(gè)元素了
篩選成立的組合
假定有人為你提供了三元數(shù)中的前兩個(gè)數(shù)字: a 和 b 。怎么知道它是否能形成一組勾股數(shù)呢半醉?你需要測(cè)試 a * a + b * b 的平方根是不是整數(shù),也就是說(shuō)它沒(méi)有小數(shù)部分——在Java里可以使用 expr % 1 表示麦射。如果它不是整數(shù)罗售,那就是說(shuō) c 不是整數(shù)职恳。你可以用filter 操作表達(dá)這個(gè)要求
filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
生成三元組
在篩選之后,你知道 a 和 b 能夠組成一個(gè)正確的組合⌒暇猓現(xiàn)在需要?jiǎng)?chuàng)建一個(gè)三元組。你可以使用map 操作,像下面這樣把每個(gè)元素轉(zhuǎn)換成一個(gè)勾股數(shù)組
tream.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
.map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});
生成 b 值
需要生成 b 的值钳枕。前面已經(jīng)看到, Stream.rangeClosed 讓你可以在給定區(qū)間內(nèi)生成一個(gè)數(shù)值流。你可以用它來(lái)給 b 提供數(shù)值,這里是1到100
IntStream.rangeClosed(1, 100)
.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
.boxed()
.map(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});
你在 filter 之后調(diào)用 boxed ,從 rangeClosed 返回的 IntStream 生成一個(gè)Stream<Integer> 。這是因?yàn)槟愕?map 會(huì)為流中的每個(gè)元素返回一個(gè) int 數(shù)組。而 IntStream中的 map 方法只能為流中的每個(gè)元素返回另一個(gè) int ,這可不是你想要的鸽粉!你可以用 IntStream的 mapToObj 方法改寫(xiě)它椒舵,這個(gè)方法會(huì)返回一個(gè)對(duì)象值流:
IntStream.rangeClosed(1, 100)
.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
.mapToObj(b -> new int[]{a, b, (int) Math.sqrt(a * a + b * b)});
生成值
給出了 a 的值炬灭。 現(xiàn)在鼻吮,只要已知 a 的值,你就有了一個(gè)可以生成勾股數(shù)的流士鸥。如何解決這個(gè)問(wèn)題呢鲤脏?就像 b 一樣称近,你需要為 a 生成數(shù)值岖常!最終的解決方案如下所示
Stream<int[]> pythagoreanTriples =
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a ->
IntStream.rangeClosed(a, 100)
.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
.mapToObj(b ->
new int[]{a, b, (int)Math.sqrt(a * a + b * b)})
);
flatMap 又是怎么回事呢?首先晒夹,創(chuàng)建一個(gè)從1到100的數(shù)值范圍來(lái)生成 a 的值裆馒。對(duì)每
個(gè)給定的 a 值姊氓,創(chuàng)建一個(gè)三元數(shù)流。要是把 a 的值映射到三元數(shù)流的話喷好,就會(huì)得到一個(gè)由流構(gòu)成的流翔横。 flatMap 方法在做映射的同時(shí),還會(huì)把所有生成的三元數(shù)流扁平化成一個(gè)流梗搅。這樣你就得到了一個(gè)三元數(shù)流禾唁。還要注意,我們把 b 的范圍改成了 a 到100无切。沒(méi)有必要再?gòu)?開(kāi)始了荡短,否則就會(huì)造成重復(fù)的三元數(shù),例如(3,4,5)和(4,3,5)
最終實(shí)現(xiàn):
public static void main(String[] args) {
Stream<int[]> pythagoreanTriples =
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a ->
IntStream.rangeClosed(a, 100)
.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
.mapToObj(b ->
new int[]{a, b, (int)Math.sqrt(a * a + b * b)})
);
pythagoreanTriples.limit(5)
.forEach(t ->
System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
}
構(gòu)建流
由值創(chuàng)建流
可以使用靜態(tài)方法 Stream.of 订雾,通過(guò)顯式值創(chuàng)建一個(gè)流肢预。它可以接受任意數(shù)量的參數(shù)
例子:
public class M1 {
//以下代碼直接使用 Stream.of 創(chuàng)建了一個(gè)字符串流。然后洼哎,你可以將字符串轉(zhuǎn)換為大寫(xiě)烫映,再
//一個(gè)個(gè)打印出來(lái)
public static void main(String[] args) {
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
}
}
由數(shù)組創(chuàng)建流
可以使用靜態(tài)方法 Arrays.stream 從數(shù)組創(chuàng)建一個(gè)流。它接受一個(gè)數(shù)組作為參數(shù)
public class M1 {
//將一個(gè)原始類型 int 的數(shù)組轉(zhuǎn)換成一個(gè) IntStream
public static void main(String[] args) {
int[] nums = {1,2,5,96,4556,5,6,5};
int sum = Arrays.stream(nums).sum();
System.out.println(sum);
}
}
由文件生成流
Java中用于處理文件等I/O操作的NIO API(非阻塞 I/O)已更新噩峦,以便利用Stream API锭沟。java.nio.file.Files 中的很多靜態(tài)方法都會(huì)返回一個(gè)流
public class M1 {
//,一個(gè)很有用的方法是
//Files.lines 识补,它會(huì)返回一個(gè)由指定文件中的各行構(gòu)成的字符串流族淮。使用你迄今所學(xué)的內(nèi)容,
//你可以用這個(gè)方法看看一個(gè)文件中有多少各不相同的詞:
public static void main(String[] args) {
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(IOException e){
}
}
}
由函數(shù)生成流:創(chuàng)建無(wú)限流
Stream API提供了兩個(gè)靜態(tài)方法來(lái)從函數(shù)生成流: Stream.iterate 和Stream.generate
這兩個(gè)操作可以創(chuàng)建所謂的無(wú)限流:不像從固定集合創(chuàng)建的流那樣有固定大小的流凭涂。由 iterate和 generate 產(chǎn)生的流會(huì)用給定的函數(shù)按需創(chuàng)建值祝辣,因此可以無(wú)窮無(wú)盡地計(jì)算下去!一般來(lái)說(shuō)切油,應(yīng)該使用 limit(n) 來(lái)對(duì)這種流加以限制蝙斜,以避免打印無(wú)窮多個(gè)值
迭代
public class M1 {
public static void main(String[] args) {
Stream.iterate(0,n-> n + 2)
.limit(10)
.forEach(System.out::println);
}
// iterate 方法接受一個(gè)初始值(在這里是 0 ),還有一個(gè)依次應(yīng)用在每個(gè)產(chǎn)生的新值上的
//Lambda( UnaryOperator<t> 類型)澎胡。這里孕荠,我們使用Lambda n -> n + 2 ,返回的是前一個(gè)元
//素加上2攻谁。因此稚伍, iterate 方法生成了一個(gè)所有正偶數(shù)的流:流的第一個(gè)元素是初始值 0 。然后加
//上 2 來(lái)生成新的值 2 戚宦,再加上 2 來(lái)得到新的值 4 个曙,以此類推。這種 iterate 操作基本上是順序的受楼,
//因?yàn)榻Y(jié)果取決于前一次應(yīng)用
}
此操作將生成一個(gè)無(wú)限流——這個(gè)流沒(méi)有結(jié)尾垦搬,因?yàn)橹凳前葱栌?jì)算的渔肩,可以永遠(yuǎn)計(jì)算下去八孝。我們說(shuō)這個(gè)流是無(wú)界的躺翻。正如我們前面所討論的牺荠,這是流和集合之間的一個(gè)關(guān)鍵區(qū)別。我們使用 limit 方法來(lái)顯式限制流的大小糟趾。這里只選擇了前10個(gè)偶數(shù)慌植。然后可以調(diào)用 forEach 終端操作來(lái)消費(fèi)流,并分別打印每個(gè)元素
生成
與 iterate 方法類似义郑, generate 方法也可讓你按需生成一個(gè)無(wú)限流蝶柿。但 generate 不是依次對(duì)每個(gè)新生成的值應(yīng)用函數(shù)的。它接受一個(gè) Supplier<T> 類型的Lambda提供新的值
例子:
這段代碼將生成一個(gè)流非驮,其中有五個(gè)0到1之間的隨機(jī)雙精度數(shù)
public static void main(String[] args) {
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
}