前言:在慕課網上學習 一課掌握Lambda表達式語法及應用 所記的筆記,供本人複習之用。
目錄
第一章 爲什麼引入Lambda表達式
1.1 什麼是Lambda
1.2 Model Code as Data
1.3 功能接口的設計及優化
在傳統模式用匿名內部類實現,線程的創建需要很多行,其實真正有效的代碼就一行。
如果用lambda表達式就會比較簡潔
1.4 爲什麼要用Lambda表達式
第二章 Lambda表達式基礎知識
2.1 函數式接口
函數式接口,就是Java類型系統中只包含一個接口方法的特殊接口,Java提供了語義化檢測註解@FunctionalInterface來進行檢測函數式接口的合法性。
當接口中有兩個接口方法時便會報錯,但可以有多個靜態方法和默認方法和從Object繼承過來的方法。
2.1.1 基本使用
對於一個函數式接口
匿名內部類實現:
lambda表達式實現:
2.1.2 jdk中常見的函數式接口
Jdk8提供了java.util.function包,提供了常用的函數式功能接口。
1.Predicate
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
//省略...
}
例子:
2. Consumer
接收一個T類型的參數,不返回任何結果。
public interface Consumer<T> {
void accept(T t);
//省略...
}
例子:
3.Function
接收參數對象T,返回結果對象R
public interface Function<T, R> {
R apply(T t);
//省略...
}
例子:
4.Supplier
不接收參數,提供T對象的創建工廠
public interface Supplier<T> {
T get();
}
例子:
5.UnaryOperator
接收參數對象T,返回結果對象T
public interface UnaryOperator<T> extends Function<T, T> {
//省略...
}
例子:
6.BinaryOperator
接收兩個T對象,返回一個T對象結果
總結:
這些是我們在常規的項目處理中經常用到的不同場景的函數式接口
2.2 Lambda語法及使用
基本語法:
2.2.1 變量的訪問操作
匿名內部類:
在匿名內部類中,this代表的是匿名內部類,而不是App2這個類。
lamdba表達式:
s1是類中的全局變量,this指代的就是類App2而不是lambda語句塊。
2.3 Lambda表達式運行原理
2.3.1 lambda表達類型檢查
表達式類型檢查:
聲明函數式接口:
將MyInterface類型作爲函數的參數
lambda表達式實現該參數
lambda表達式的類型檢查:
當我們將(x,y)->{..}交給test(param)參數時,JVM會推導param參數是一個MyInterface類型的參數,所以當前的lambda表達式屬於MyInterface類型,MyInterface接口就是lambda表達式的目標類型。
參數類型檢查:
(x,y)->{..} -->MyInterface.strategy(T r,R r),strategy函數需要一個T類型和一個R類型,我們在把MyInterface當作類型傳遞給參數時,確定了它的T類型和R類型,確定了它的T類型爲String,R類型爲List,然後與lambda表達式進行推導驗證,會得出(x,y)執行的就是strategy(T t,R r)這樣一個方法,所以最終推導出來x是屬於T類型即String,y屬於R類型即List。
假如是如下情況,因爲由類型推導,所以不會編譯成功
總結:JVM會根據代碼在運行過程中的上下文進行檢測,在這裏test需要一個MyInterface類型的參數,在調用test時我們傳遞了一個lambda表達式,MyInterface就是lambda表達式的目標類型,接下來會繼續根據lambda表達式與綁定的接口進行類型參數的推導,在類型參數進行推導時,會驗證lambda表達式中的參數個數與順序是否和接口中定義的參數類型和順序一致,一致的情況下按照參數的順序進行確認。
2.3.2 方法重載與lambda表達式
匿名內部類實現:
lambda表達式實現:
有問題,因爲有類型檢查,而它不知道實現的是哪一個接口
2.3.3 lambda表達式底層構建原理
驗證:
建立類:
反編譯:
第三章 lamdba表達式高級拓展
3.1 方法引用
靜態方法引用的使用
類型名稱.方法名稱() 到 類型名稱::方法名稱
實例方法的使用
創建對象,加雙冒號引用
構造方法引用
它需要綁定函數式接口
如:
綁定後,執行對應方法便會返回一個Person對象。
注:方法名不用一樣,參數返回值一樣就行
3.2 Stream概述
這裏的Stream既不是IO中的數據流Stream,也不是集合元素,也不是數據結構不能存儲數據,這裏講的Stream是和數據算法與運算相關的,JDK8中stream流的引入是針對多個數據、數組、容器、集合等存儲批量數據的容器聚合操作時複雜冗餘的流程而提出的一套新的api,可以結合lambda表達式進行串行或者並行兩種不同的方式完成對批量數據的增強操作。
爲什麼要學習stream,例如找出account大於5的賬號
3.2 streamAPI
stream的處理流程:
獲取到數據源,數據轉換,獲取結果
3.2.1 獲取stream對象
3.2.2 中間操作API{ intermediate}
操作結果是一個stream,中間操作可以有一個或者多個連續的中間操作,需要注意的是,中間操作只記錄操作方式,不做具體執行,直到結束操作發生時,才做數據的最終執行。
中間操作過程:無狀態:數據處理時,不受前置中間操作的影響,主要包括map/filter/peek/parallel/sequential/unordered
有狀態:數據處理時,受到前置中間操作的影響,主要包括distinct/sorted/limit/skip
3.2.3 終結操作|結束操作{Terminal}
需要注意:一個Stream對象,只能有一個Terminal操作,這個操作一旦發生,就會真實處理數據,生成對應的
終結操作:非短路操作:當前的stream對象必須處理完集合中所有數據,才能得到處理結果。主要包括
短路操作:當前的stream對象在處理過程中,一旦滿足某個條件,就可以得到結果。
3.3 Stream操作集合中的數據
3.3.1 其它類型->stream對象
在數據運算中,會對基本數據類型進行頻繁的裝箱拆箱操作,所以對於基本類型stream進行了一些基本的封裝。
在這裏的處理過程中,如果我們是通過基本的編碼進行操作,new int時會出現裝箱的操作,數據處理的時候又會出現拆箱的操作,在一套完整的算法中,裝箱拆箱就會頻繁出現,stream將它封裝在了底層,封裝了在做了中間操作中,最終只要完成一次裝箱拆箱就可以了。
同樣提供了一些基本的數據功能
3.3.2 stream對象->其它類型
3.3.3 stream常見API操作
map:
結合原本的值做一些操作,返回新的值
filter:
刪除不符合條件的
peek:
上面的方式不大友好,迭代過程出現冗餘,用peek進行下面的操作,迭代過程只發生了一次。
skip:
limit:
跳3條,做兩條
distinct:
reduce:
合併數據並處理
sum是每次迭代後得到的結果,這裏是每次相加後得到的結果。
3.4 Stream的執行效率問題
使用五種方法進行速度的測試,分別是stream,parallelStream,普通for循環,增強型for循環,迭代器
3.4.1 基本類型
stream:
parallelStream:
普通for循環:
增強型for循環:
迭代器:
一次執行結果:
3.4.2 複雜類型
類似上面的操作,只不過是找一個擁有最大屬性的對象。
時間爲:
3.5 Stream線程安全問題
在並行Stream的操作過程中,參考頂層運行原理,其實是將一個操作中的每個部分拆分成了多個子任務,通過多個線程執行的過程,也就是將一個大任務拆分成了幾個小任務,通過線程進行完善的過程,最終將多個小任務進行合併。
在處理時,如果是串行stream,我們完全可以通過自定義多線程程序中的邏輯代碼進行數據的同步來完成數據訪問的控制,但是並行stream引發的多線程對於數據源是否存在線程安全的問題?
首先我們向list中添加1000個數
串行stream遍歷並添加數據到list2中
並行stream遍歷並添加數據到list3中
結果如下,當前Stream操作的集合並不是線程安全的,所以多個線程訪問共享數據出現了衝突,最後得到的數據便會丟失。
查看官方文檔:
當前並行的stream因爲使用的collections就不是線程安全的,所以意味着在多線程操作的情況下,可能會因爲多線程的處理過程導致一些數據不一致的錯誤,在處理的過程中,Collection提供了一些線程同步塊,通過Collection集合本身提供的線程同步塊,我們可以完成對於數據的同步處理,另外我們需要注意的是,如果使用Collection本身提供的線程同步塊的話,會引起線程競爭的問題,如果我們想避免線程競爭的問題,聚合操作和parallel stream結合到一起能讓我們得到一個在非線程安全的情況下對於數據的處理過程,要求是我們在操作的過程中,對於非線程安全集合中的數據不能進行修改(本人沒咋看懂這段)。
另一個文檔:
forEach:
我們可以用collect與reduct這樣的線程安全終端操作,它們即使操作不線程安全的集合也不會出現問題。如: