快速掌握J(rèn)ava8 Stream函數(shù)式編程技巧

image

函數(shù)式編程優(yōu)勢

  • “函數(shù)第一位”硕勿,即函數(shù)可以出現(xiàn)在任何地方哨毁。
  • 可以把函數(shù)作為參數(shù)傳遞給另一個函數(shù),還可以將函數(shù)作為返回值源武。
  • 讓代碼的邏輯更清晰更優(yōu)雅扼褪。
  • 減少了可變量(Immutable Variable)的聲明,程序更為安全粱栖。
  • 支持惰性計算话浇。

Lambda語法三部分

  • 一個括號內(nèi)用逗號分隔的形式參數(shù),參數(shù)是函數(shù)式接口里面方法的參數(shù)
  • 一個箭頭符號:->
  • 方法體闹究,可以是表達(dá)式和代碼塊幔崖,方法體函數(shù)式接口里面方法的實(shí)現(xiàn),如果是代碼塊,則必須用{}來包裹起來赏寇,且需要一個return 返回值吉嫩,但有個例外,若函數(shù)式接口里面方法返回值是void嗅定,則無需{}自娩。例如:
  • (parameters) -> expression 或者 (parameters) -> { statements; }

方法引用是lambda表達(dá)式的一個簡化寫法,所引用的方法其實(shí)是lambda表達(dá)式的方法體實(shí)現(xiàn)渠退,語法也很簡單忙迁,左邊是容器(可以是類名,實(shí)例名)碎乃,中間是“::”姊扔,右邊是相應(yīng)的方法名。如下所示:
ObjectReference::methodName

一般方法的引用格式是:

  • 如果是靜態(tài)方法梅誓,則是ClassName::methodName恰梢。如 Object ::equals
  • 如果是實(shí)例方法,則是Instance::methodName证九。如Object obj=new Object();obj::equals;
  • 構(gòu)造函數(shù).則是ClassName::new

Stream是什么

Stream是Java8中新加入的api删豺,更準(zhǔn)確的說: Java 8 中的 Stream 是對集合(Collection)對象功能的增強(qiáng),它專注于對集合對象進(jìn)行各種非常便利愧怜、高效的聚合操作呀页,或者大批量數(shù)據(jù)操作 。Stream API 借助于同樣新出現(xiàn)的 Lambda 表達(dá)式拥坛,極大的提高編程效率和程序可讀性蓬蝶。

image

以前我們處理復(fù)雜的數(shù)據(jù)只能通過各種for循環(huán)唯沮,不僅不美觀鲤竹,而且時間長了以后可能自己都看不太明白以前的代碼了场钉,但有Stream以后叶圃,通過filter,map爽待,limit等等方法就可以使代碼更加簡潔并且更加語義化藤违。 Stream的效果就像上圖展示的它可以先把數(shù)據(jù)變成符合要求的樣子(map)粥脚,吃掉不需要的東西(filter)然后得到需要的東西(collect)谍咆。

Stream操作分類

Stream上的所有操作分為兩類:中間操作和結(jié)束操作禾锤,中間操作只是一種標(biāo)記,只有結(jié)束操作才會觸發(fā)實(shí)際計算摹察。中間操作又可以分為無狀態(tài)的(Stateless)和有狀態(tài)的(Stateful)恩掷,無狀態(tài)中間操作是指元素的處理不受前面元素的影響,而有狀態(tài)的中間操作必須等到所有元素處理之后才知道最終結(jié)果供嚎,比如排序是有狀態(tài)操作黄娘,在讀取所有元素之前并不能確定排序結(jié)果峭状;結(jié)束操作又可以分為短路操作和非短路操作,短路操作是指不用處理全部元素就可以返回結(jié)果逼争,比如找到第一個滿足條件的元素优床。之所以要進(jìn)行如此精細(xì)的劃分,是因?yàn)榈讓訉γ恳环N情況的處理方式不同氮凝。

image

Stream API等價實(shí)現(xiàn)

求出字符串集合中所有以字母A開頭字符串的最大長度

int longest = 0; for(String str : strings){ if(str.startsWith("A")){// 1\. filter(), 保留以A開頭的字符串
        int len = str.length();// 2\. mapToInt(), 轉(zhuǎn)換成長度
        longest = Math.max(len, longest);// 3\. max(), 保留最長的長度
 }
}
image
int longest = strings.stream()
    .filter(str -> str.startsWith("A"))
    .mapToInt(str -> str.length()) //.mapToInt(String::length)
    .max();

Stream串行與并行

image

Stream可以分為串行與并行兩種羔巢,串行流和并行流差別就是單線程和多線程的執(zhí)行。 default Stream stream() : 返回串行流 default Stream parallelStream() : 返回并行流 stream()和parallelStream()方法返回的都是java.util.stream.Stream<E>類型的對象罩阵,說明它們在功能的使用上是沒差別的。唯一的差別就是單線程和多線程的執(zhí)行启摄。

Stream性能總結(jié)

1.對于簡單操作稿壁,比如最簡單的遍歷,Stream串行API性能明顯差于顯示迭代歉备,但并行的Stream API能夠發(fā)揮多核特性傅是。

2.對于復(fù)雜操作,Stream串行API性能可以和手動實(shí)現(xiàn)的效果匹敵蕾羊,在并行執(zhí)行時Stream API效果遠(yuǎn)超手動實(shí)現(xiàn)喧笔。

所以,如果出于性能考慮:

  • 對于簡單操作推薦使用外部迭代手動實(shí)現(xiàn)龟再。
  • 對于復(fù)雜操作书闸,推薦使用Stream API。
  • 在多核情況下利凑,推薦使用并行Stream API來發(fā)揮多核優(yōu)勢浆劲。
  • 單核情況下不建議使用并行Stream API。 如果出于代碼簡潔性考慮哀澈,使用Stream API能夠?qū)懗龈痰拇a牌借。即使是從性能方面說,盡可能的使用Stream API也另外一個優(yōu)勢割按,那就是只要Java Stream類庫做了升級優(yōu)化膨报,代碼不用做任何修改就能享受到升級帶來的好處。

參考 Java 8 Stream的性能到底如何适荣?

Stream 來源

image

所有流計算都有一種共同的結(jié)構(gòu):它們具有一個流來源现柠、0 或多個中間操作,以及一個終止操作束凑。流的元素可以是對象引用 (Stream<String>)晒旅,也可以是原始整數(shù) (IntStream)、長整型 (LongStream) 或雙精度 (DoubleStream)汪诉。

因?yàn)?Java 程序使用的大部分?jǐn)?shù)據(jù)都已存儲在集合中废恋,所以許多流計算使用集合作為它們的來源谈秫。JDK 中的 Collection 實(shí)現(xiàn)都已增強(qiáng),可充當(dāng)高效的流來源鱼鼓。但是拟烫,還存在其他可能的流來源,比如數(shù)組迄本、生成器函數(shù)或內(nèi)置的工廠(比如數(shù)字范圍)硕淑,而且可以編寫自定義的流適配器,以便可以將任意數(shù)據(jù)源充當(dāng)流來源嘉赎。如上圖中一些流生成方法置媳。

Stream 操作

中間操作負(fù)責(zé)將一個流轉(zhuǎn)換為另一個流,中間操作包括 filter()(選擇與條件匹配的元素)公条、map()(根據(jù)函數(shù)來轉(zhuǎn)換元素)拇囊、distinct()(刪除重復(fù))、limit()(在特定大小處截斷流)和 sorted()靶橱。一些操作(比如 mapToInt())獲取一種類型的流并返回一種不同類型的流寥袭。

中間操作始終是惰性的:調(diào)用中間操作只會設(shè)置流管道的下一個階段,不會啟動任何操作关霸。重建操作可進(jìn)一步劃分為無狀態(tài) 和有狀態(tài) 操作传黄。無狀態(tài)操作(比如 filter() 或 map())可獨(dú)立處理每個元素,而有狀態(tài)操作(比如 sorted() 或 distinct())可合并以前看到的影響其他元素處理的元素狀態(tài)队寇。

image

數(shù)據(jù)集的處理在執(zhí)行終止操作時開始膘掰,比如縮減(sum() 或 max())、應(yīng)用 (forEach()) 或搜索 (findFirst()) 操作英上。終止操作會生成一個結(jié)果或副作用炭序。執(zhí)行終止操作時,會終止流管道苍日,如果您想再次遍歷同一個數(shù)據(jù)集惭聂,可以設(shè)置一個新的流管道。如下給出了一些終止流操作相恃。

image

Stream 流與集合比較

集合是一種數(shù)據(jù)結(jié)構(gòu)辜纲,它的主要關(guān)注點(diǎn)是在內(nèi)存中組織數(shù)據(jù),而且集合會在一段時間內(nèi)持久存在拦耐。 流的關(guān)注點(diǎn)是計算耕腾,而不是數(shù)據(jù)。流沒有為它們處理的元素提供存儲空間杀糯,而且流的生命周期更像一個時間點(diǎn) — 調(diào)用終止操作扫俺。 不同于集合,流也可以是無限的(Stream.generate固翰、Stream.iterate)狼纬;相應(yīng)地羹呵,一些操作(limit()、findFirst())是短路疗琉,而且可在無限流上運(yùn)行有限的計算冈欢。 程序 = 數(shù)據(jù)結(jié)構(gòu) + 算法,集合即數(shù)據(jù)結(jié)構(gòu)盈简,流操作相當(dāng)于算法凑耻。

image

數(shù)據(jù)形式:集合是一個內(nèi)存中的數(shù)據(jù)結(jié)構(gòu),它包含數(shù)據(jù)結(jié)構(gòu)中目前所有的值——集合中的每個元素都得先算出來才能添加到集合中柠贤。(你可以往集合里加?xùn)|西或者刪東西香浩,但是不管什么時候,集合中的每個元素都是放在內(nèi)存里的种吸,元素都得先算出來才能成為集合的一部分弃衍。) 相比之下,流則是在概念上固定的數(shù)據(jù)結(jié)構(gòu)(你不能添加或刪除元素)坚俗,其元素則是按需計算的。

image

迭代方式:使用Collection接口需要用戶去做迭代(比如用for-each)岸裙,這稱為外部迭代猖败。相反,Streams庫使用內(nèi)部迭代——它幫你把迭代做了降允,還把得到的流值存在了某個地方恩闻,你只要給出一個函數(shù)說要干什么就可以了。Steams庫的內(nèi)部迭代可以自動選擇一種適合你硬件的數(shù)據(jù)表示和并行實(shí)現(xiàn)剧董。

Stream 基本使用

filter篩選(中間操作)

輸出:4,5

List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);

Stream<Integer> stream = integerList.stream().filter(i -> i > 3);

distinct去重(中間操作)

輸出:1,2,3,4,5

List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);

Stream<Integer> stream = integerList.stream().distinct();

limit限制(中間操作)

輸出:1,1,2

 List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);

 Stream<Integer> stream = integerList.stream().limit(3);

skip跳過(中間操作)

輸出:2,3,4,5

 List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);

 Stream<Integer> stream = integerList.stream().skip(2);

map流映射(中間操作)

輸出:6, 7, 2, 6

List<String> stringList = Arrays.asList("Java 8", "Lambdas",  "In", "Action");

Stream<Integer> stream = stringList.stream().map(String::length);

flatMap流轉(zhuǎn)換(中間操作)

輸出:1, 2, 3, 4

將一個流中的每個值都轉(zhuǎn)換為另一個流

List<List<Integer>> lists = new ArrayList<List<Integer>>() {{
    add(Arrays.asList(1, 2));
    add(Arrays.asList(3, 4));
}};

Stream<Integer> stream = lists.stream().flatMap(List::stream); //Stream<Integer> stream = lists.stream().flatMap(list -> list.stream());

allMatch匹配所有(中間操作)

輸出:

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().allMatch(i -> i > 3)) {
    System.out.println("值都大于3");
}

noneMatch全部不匹配(中間操作)

輸出:

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().noneMatch(i -> i > 3)) {
    System.out.println("值都小于3");
}

anyMatch匹配其中一個(中間操作)

輸出:存在大于3的值

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().anyMatch(i -> i > 3)) {
    System.out.println("存在大于3的值");
}

findFirst查找第一個(終端操作)

輸出:4

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> result = integerList.stream().filter(i -> i > 3).findFirst();

findAny隨機(jī)查找一個(終端操作)

輸出:存在大于3的值

和findFirst操作相比幢尚,并行流優(yōu)勢更大

List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().findAny(i -> i > 3)) {
    System.out.println("存在大于3的值");
}

Stream 常用統(tǒng)計

List<Integer> ints = Arrays.asList(1, 1, 2, 2, 3); //統(tǒng)計流中元素個數(shù)
ints.stream().count();
ints.stream().collect(Collectors.counting()); //獲取流中最小值
ints.stream().min(Integer::compareTo);
ints.stream().collect(Collectors.minBy(Integer::compareTo)); //獲取流中最大值
ints.stream().max(Integer::compareTo);
ints.stream().collect(Collectors.maxBy(Integer::compareTo)); //求和
ints.stream().mapToInt(Integer::intValue).sum();
ints.stream().collect(Collectors.summingInt(Integer::intValue));
ints.stream().reduce(0, Integer::sum); //平均值
ints.stream().collect(Collectors.averagingInt(Integer::intValue)); //通過summarizingInt同時求總和、平均值翅楼、最大值尉剩、最小值
ints.stream().collect(Collectors.summarizingInt(Integer::intValue));

Stream 終端操作(collect)

List<Integer> ints = Arrays.asList(1, 1, 2, 2, 3); //返回List
ints.stream().collect(Collectors.toList()); //返回Set
ints.stream().collect(Collectors.toSet()); //返回Map
ints.stream().collect(Collectors.toMap(k -> k, v -> v, (v1, v2) -> v1)); //group分組
ints.stream().collect(Collectors.groupingBy(k -> k)); //partitioningBy分區(qū)
ints.stream().collect(Collectors.partitioningBy(k -> k % 2 == 0));

Stream參考文獻(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末毅臊,一起剝皮案震驚了整個濱河市理茎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌管嬉,老刑警劉巖皂林,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蚯撩,居然都是意外死亡础倍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門胎挎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沟启,“玉大人忆家,你說我怎么就攤上這事∶榔郑” “怎么了弦赖?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長浦辨。 經(jīng)常有香客問我蹬竖,道長,這世上最難降的妖魔是什么流酬? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任币厕,我火速辦了婚禮,結(jié)果婚禮上芽腾,老公的妹妹穿的比我還像新娘旦装。我一直安慰自己,他們只是感情好摊滔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布阴绢。 她就那樣靜靜地躺著,像睡著了一般艰躺。 火紅的嫁衣襯著肌膚如雪呻袭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天腺兴,我揣著相機(jī)與錄音左电,去河邊找鬼。 笑死页响,一個胖子當(dāng)著我的面吹牛篓足,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播闰蚕,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼栈拖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了陪腌?” 一聲冷哼從身側(cè)響起辱魁,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诗鸭,沒想到半個月后染簇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡强岸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年锻弓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝌箍。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡青灼,死狀恐怖暴心,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情杂拨,我是刑警寧澤专普,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站弹沽,受9級特大地震影響檀夹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜策橘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一炸渡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丽已,春花似錦蚌堵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嘁灯,卻和暖如春宫仗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背旁仿。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留孽糖,地道東北人枯冈。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像办悟,于是被迫代替她去往敵國和親尘奏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354