【閱讀】Java8函數(shù)式編程--編程范式的趨勢

讓我們來說說函數(shù)式編程--Lambda表達式
上周開始讨惩,看了這本書--Java 8 函數(shù)式編程产弹,以下是我的讀書摘抄剑勾。

背景知識:

函數(shù)接口:只有一個抽象方法的接口

方法簽名:由方法名+形參列表構(gòu)成+返回值類型量窘。JVM是并沒有特別明確的將數(shù)據(jù)類型寫出來雇寇,而是提供了特殊的表示法。

惰性求值方法: 只描述Stream蚌铜,不產(chǎn)生實際執(zhí)行的方法

及早求值: 立刻執(zhí)行

函數(shù)的副作用:當調(diào)用函數(shù)時锨侯,除了返回函數(shù)值之外,還對主調(diào)用函數(shù)產(chǎn)生附加的影響(例如修改全局變量或修改參數(shù))

純函數(shù):輸入輸出數(shù)據(jù)流全是顯式的(顯式的含義是:函數(shù)與外界交換數(shù)據(jù)只有一個唯一渠道——參數(shù)和返回值冬殃;函數(shù)從函數(shù)外部接受的所有輸入信息都通過參數(shù)傳遞到該函數(shù)內(nèi)部囚痴;函數(shù)輸出到函數(shù)外部的所有信息都通過返回值傳遞到該函數(shù)外部)

非純函數(shù):與純函數(shù)相反,函數(shù)通過參數(shù)和返回值以外的渠道审葬,和外界進行數(shù)據(jù)交換深滚。比如,讀取全局變量涣觉,修改全局變量痴荐。

阿姆達爾定律:對于固定負載情況下描述并行處理效果的加速比s,S=1/(1-a+a/n)旨枯,其中a為并行計算部分所占比例蹬昌,n為并行處理結(jié)點個數(shù)

正文

Lambda表達式

在思考問題時,使用不可變值和函數(shù)攀隔,函數(shù)對一個值進行處理皂贩,映射成另一個值

Lambda表達式

  1. 傳入一段代碼塊
  2. 不需要顯式聲明/指定參數(shù)類型(編譯器可以通過方法簽名來推斷類型)

Lambda表達式引用的局部變量必須是final或既成事實上的final變量(基本類型只能賦值一次栖榨,對象的引用不能改變)

Stream API

意義和創(chuàng)新:遍歷從外部迭代變成了內(nèi)部迭代。把程序扮演Director導演類的部分邏輯和操作提取明刷、轉(zhuǎn)移到了類庫中

實現(xiàn)機制

Stream操作分為惰性求值和及早求值婴栽;形成一個惰性求值的鏈,最后用一個及早求值的操作返回想要的結(jié)果辈末。

整個過程被分解為多個惰性求值操作和一個單個及早求值操作愚争,而這些多個操作只需要遍歷一次。整個過程和建造者模式有共同之處挤聘。建造者模式使用一系列操作來設置屬性并配置轰枝,最后調(diào)用build方法,這時组去,對象才被真正創(chuàng)建鞍陨。

常用的流操作

collect:流生成集合

map:改變流中元素的類型

flatMap:把最初流中的一個元素轉(zhuǎn)成一個流

filter:過濾流中的元素

Max/Min:求極值,返回 Optional (涉及比較器類Comparator从隆,因為比較意味著排序诚撵,排序意味著需要一個排序的指標)

reduce

Lambda表達式正確使用姿勢

1)明確要達成什么轉(zhuǎn)化,而不是說明如何轉(zhuǎn)化

2)寫出的函數(shù)盡量沒有副作用

純函數(shù)的優(yōu)點
  1. 無狀態(tài)键闺,線程安全寿烟。不需要線程同步。
  2. 純函數(shù)相互調(diào)用組裝起來的函數(shù)辛燥,還是純函數(shù)筛武。
  3. 可緩存結(jié)果,原生支持并行(不懂)

I/O API可以看作是一種特殊的全局變量购桑。文件畅铭、屏幕、數(shù)據(jù)庫等輸入輸出結(jié)構(gòu)可以看作是獨立于運行環(huán)境之外的系統(tǒng)外全局變量勃蜘,而不是應用程序自己定義的全局變量。

類庫

裝箱類型在Lambda表達式上的體現(xiàn)

  1. int整型在內(nèi)存中占用4個字節(jié)假残,Integer在內(nèi)存中占用16字節(jié)缭贡;
  2. 基本類型和裝箱類型相互轉(zhuǎn)化需要額外的計算開銷
  3. 在Java中想要一個包含整型值的列表List<int>,實際上得到的卻是一個包含整型對象的列表List<Integer>;

對于需要大量數(shù)值運算的算法來說辉懒,由于上述的原因阳惹,減緩了程序的運行速度

因此,Stream類的某些方法對基本類型和裝箱類型做了區(qū)分眶俩;Java 8對整型莹汤,長整型和雙浮點型做了特殊處理;方法有:mapToLong(),MapToInt();

Lambda表達式的重載

Lambda表達式的類型就是對應的函數(shù)接口類型颠印。遇到重載時調(diào)用準則:

  1. 優(yōu)先執(zhí)行最具體的類型
  2. 如果最具體的類型有多個纲岭,則需要認為指定類型

默認方法

Java 8新增一個關(guān)鍵詞default抹竹,在接口中,可以存在由default修飾的默認方法(也就是說止潮,Java 8接口里窃判,不再只能是抽象方法,具體的方法也可以存在即默認方法喇闸;同時袄琳,也允許存在靜態(tài)方法),當子類不實現(xiàn)這個接口的某個抽象方法時燃乍,子類就是使用該默認方法唆樊。

還要注意一點:一個類要實現(xiàn)的接口里,存在默認方法和它父類的方法相同刻蟹,則優(yōu)先選擇父類定義的方法窗轩。

歸納出兩條簡單的定律:

  1. 類勝于接口。如果在繼承鏈中有方法或抽象方法聲明座咆,那么久可以忽略接口中定義的方法
  2. 子類勝過父類痢艺。如果一個接口繼承了另一個接口,且兩個接口都定義了一個默認的方法介陶,那么子類中定義的方法勝出堤舒。
Optional

該新的數(shù)據(jù)類型,用來替換null哺呜。Optional對象相當于值的容器舌缤,類似ThreadLocal用法

  1. 創(chuàng)建Optional對象:Optional.of()
  2. 獲取值:get();
  3. 獲取可能存在的空值:empty().
  4. 判斷Optional對象理是否有值:isPresent();
  5. orElse(T t),當Optional對象為空時,提供一個備選值t
  6. orElseGet(Supplier supplier):當Optional對象為空時某残,調(diào)用這個函數(shù)接口

特殊的修飾

@FunctionalInterface

該注釋會強制javac檢查一個接口是否符合函數(shù)接口的標準(一個接口国撵,只有一個抽象方法),如果該注釋添加給一個枚舉類型玻墅,類或另一個注釋介牙,或者接口含不止一個抽象方法,javac就會報錯(編譯的時候)澳厢。重構(gòu)代碼時环础,使用它能很容易發(fā)現(xiàn)問題。

高級集合類

方法引用

本小節(jié)摘自【譯】Java 8的新特性—終極版

方法引用使得開發(fā)者可以直接引用現(xiàn)存的方法剩拢、Java類的構(gòu)造方法或者實例對象线得。方法引用和Lambda表達式配合使用,使得java類的構(gòu)造方法看起來緊湊而簡潔徐伐,沒有很多復雜的模板代碼贯钩。

西門的例子中,Car類是不同方法引用的例子,可以幫助讀者區(qū)分四種類型的方法引用角雷。

public static class Car {
public static Car create( final Supplier< Car > supplier ) {
    return supplier.get();
}              

public static void collide( final Car car ) {
    System.out.println( "Collided " + car.toString() );
}

public void follow( final Car another ) {
    System.out.println( "Following the " + another.toString() );
}

public void repair() {   
    System.out.println( "Repaired " + this.toString() );
}
}

第一種方法引用的類型是構(gòu)造器引用祸穷,語法是Class::new,或者更一般的形式:Class<T>::new谓罗。注意:這個構(gòu)造器沒有參數(shù)粱哼。

final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );

第二種方法引用的類型是靜態(tài)方法引用,語法是Class::static_method檩咱。注意:這個方法接受一個Car類型的參數(shù)揭措。

cars.forEach( Car::collide );

第三種方法引用的類型是某個類的成員方法的引用,語法是Class::method刻蚯,注意绊含,這個方法沒有定義入?yún)ⅲ?/p>

cars.forEach( Car::repair );

第四種方法引用的類型是某個實例對象的成員方法的引用,語法是instance::method炊汹。注意:這個方法接受一個Car類型的參數(shù):

final Car police = Car.create( Car::new );
cars.forEach( police::follow );

數(shù)據(jù)并行化

并發(fā)躬充,并行;數(shù)據(jù)并行化讨便;任務并行化

調(diào)用parallelstream()/parallel(),生成一個并行化流

收集器

Collectors.toList()充甚,Collectors.toSet,Collectors.to

調(diào)試Lambda表達式

對于業(yè)務穩(wěn)定,生命周期較長的產(chǎn)品霸褒,可以引入單元測試伴找。

程序員寫出的程序bug,有兩種

  1. 實現(xiàn)業(yè)務邏輯的思路錯誤
  2. 實現(xiàn)業(yè)務邏輯的思路對了废菱,但是在實現(xiàn)過程中出了問題技矮,開發(fā)者設想的過程和真實的情況不同。

軟件產(chǎn)品種殊轴,大部分bug來源于第二種衰倦。我們對單元測試,下一個定義:單元測試是測試一段代碼的行為是否符合預期的方式旁理。我們對每一個方法輸出結(jié)果都存在一個預期樊零,這個預期是我們在實現(xiàn)業(yè)務過程中的一個環(huán)節(jié),這些環(huán)節(jié)全部累加起來韧拒,每個環(huán)節(jié)的結(jié)果都是我們想要的淹接,那么這個業(yè)務我們就能正確的實現(xiàn)了。因此叛溢,單元測試,能檢測出第二種bug劲适;

對重構(gòu)Lamdba表達式的思考

如果存在多個方法楷掉,每個方法的處理過程相同,輸入輸出類型相同,不同的是操作的具體邏輯烹植,則可以把相同的操作提取成新的方法斑鸦,不同的操作通過方法入?yún)眢w現(xiàn)。比如下面兩個方法:

計算音樂家數(shù)量

public long countMusicians(){
    return albums.stream()
                    .mapToLong(albu ->.getMusicians().count)
                    .sum()
}

計算單曲數(shù)量

public long countTracks(){
    return albums.stream()
                    .mapToLong(albu ->.getTracks().count)
                    .sum()
}   

要怎么重構(gòu)呢草雕?

首先我們定義一個函數(shù)接口巷屿,輸入Album類,輸出一個long型, 引入泛型(Java 8其實已經(jīng)定義了這種輸入輸出的接口了)

public interface ToLongFunction<T>{
    long applyAsLong(T value);
}

我們知道墩虹,Lambda表達式返回類型是函數(shù)接口嘱巾,因此上述的兩個方法就可以重構(gòu)成下面的樣子

public long countMusicians(){
    return countFeature(ablum -> album.getMusicians().count())
}

public long countTracks(){
    return countFeature(ablum -> ablum.getTracks().count())
}

private long countFeature(ToLongFuction<Album> function){
    return albums.stream()
                    .mapToLong(function)
                    .sum();
}

整個思考過程,和非函數(shù)式編程是類似的诫钓。

對惰性求值方法的調(diào)試

Java 8 提供了一個方法peek()旬昭,舉個例子

Stream.of("one", "two", "three", "four")
        .peek(e -> System.out.println("Peeked value: " + e))
        .map(String::toUpperCase)
        .peek(e -> System.out.println("Mapped value: " + e))
        .collect(Collectors.toList());

這樣就能對流的每一個元素處理的先后值的變化進行觀察了

歡迎關(guān)注CodeThings公眾號
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市菌湃,隨后出現(xiàn)的幾起案子问拘,更是在濱河造成了極大的恐慌,老刑警劉巖惧所,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骤坐,死亡現(xiàn)場離奇詭異,居然都是意外死亡下愈,警方通過查閱死者的電腦和手機纽绍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來驰唬,“玉大人顶岸,你說我怎么就攤上這事〗斜啵” “怎么了辖佣?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長搓逾。 經(jīng)常有香客問我卷谈,道長,這世上最難降的妖魔是什么霞篡? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任世蔗,我火速辦了婚禮,結(jié)果婚禮上朗兵,老公的妹妹穿的比我還像新娘污淋。我一直安慰自己,他們只是感情好余掖,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布寸爆。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪赁豆。 梳的紋絲不亂的頭發(fā)上仅醇,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機與錄音魔种,去河邊找鬼析二。 笑死,一個胖子當著我的面吹牛节预,可吹牛的內(nèi)容都是我干的叶摄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼心铃,長吁一口氣:“原來是場噩夢啊……” “哼准谚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起去扣,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤柱衔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后愉棱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唆铐,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年奔滑,在試婚紗的時候發(fā)現(xiàn)自己被綠了艾岂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡朋其,死狀恐怖王浴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情梅猿,我是刑警寧澤氓辣,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站袱蚓,受9級特大地震影響钞啸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜喇潘,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一体斩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧颖低,春花似錦絮吵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扼褪。三九已至想幻,卻和暖如春粱栖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背脏毯。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工闹究, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人食店。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓渣淤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吉嫩。 傳聞我的和親對象是個殘疾皇子价认,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Java8 in action 沒有共享的可變數(shù)據(jù),將方法和函數(shù)即代碼傳遞給其他方法的能力就是我們平常所說的函數(shù)式...
    鐵牛很鐵閱讀 1,231評論 1 2
  • 原文鏈接:https://github.com/EasyKotlin 值就是函數(shù)自娩,函數(shù)就是值用踩。所有函數(shù)都消費函數(shù),...
    JackChen1024閱讀 5,974評論 1 17
  • 第一部分Common Lisp介紹第1章 介紹一下Lisp你在學的時候覺得已經(jīng)明白了忙迁,寫的時候更加確信了解了脐彩,教別...
    geoeee閱讀 2,946評論 5 8
  • Jav8中,在核心類庫中引入了新的概念姊扔,流(Stream)惠奸。流使得程序媛們得以站在更高的抽象層次上對集合進行操作。...
    仁昌居士閱讀 3,638評論 0 6
  • 前言 人生苦多恰梢,快來 Kotlin 佛南,快速學習Kotlin! 什么是Kotlin嵌言? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,209評論 9 118