本文是針對於java8引入函數式編程概念以及stream流相關的一些簡單介紹
什麼是函數式編程?
java程序員第一反應可能會理解成類的成員方法一類的東西
此處並非這個含義,更接近是數學上的函數
看一下百度百科中關於函數的說明
函數的定義:
給定一個數集A,假設其中的元素爲x。
現對A中的元素x施加對應法則f,記做f(x),獲得另外一數集B。假設B中的元素爲y。
則y與x之間的等量關係能夠用y=f(x)表示。
咱們把這個關係式就叫函數關係式,簡稱函數。
函數概念含有三個要素:定義域A、值域C和對應法則f。
其中核心是對應法則f,它是函數關係的本質特徵。
|
對應於編程來講,固然不是徹底的數學上的函數定義
所謂函數式編程咱們能夠理解爲:
經過對應法則f(x) 對指定的x 進行處理,映射成另一個值
並且不會對x自己產生變更
|
所謂不會對x產生變更,你能夠理解爲無反作用,或者說反作用不會被察覺
反作用你能夠理解爲解題過程當中對數據的修改
提及來好像很囉嗦,可是若是有人告訴你 經過sin(x) 計算後, x的值被改變了,你不會以爲異常奇怪麼
函數式編程就是把函數的一些特性應用於編程語言之中
注意:
函數式編程不是某一種語言,也不是某個API
他是一種方法論,是一種編程範式,有它自有的一些特性和規定
語言中引入函數式編程,也就是用語言自己定義了函數式編程的一些特性和規定
|
函數式編程最重要的基礎是λ演算,並且λ演算的函數能夠接受函數看成輸入(參數)和輸出(返回值)。
它一套用於研究函數定義、函數應用和遞歸的形式系統
咱們只須要知道λ演算是一種形式的匿名函數,而且接收一個參數做爲輸入 (能夠柯里化進行參數轉換多參數函數轉換爲單參數)
有興趣的能夠去探究下λ演算
函數式編程有下列特性
閉包和高階函數
閉包就是可以讀取其餘函數內部變量的函數,是個不太好理解的概念
此處咱們僅僅理解成 函數能夠當作值進行傳遞而且可使用變量保存 是"第一等公民"
一等公民或者一等類型的含義就是指能夠跟值同樣的地位,做爲參數傳遞或者存儲於變量中
高階函數是指能夠用另外一個函數(間接地,用一個表達式) 做爲其輸入參數,好比 f(g(x))=g(x)+1 的形式
惰性計算
表達式不是在綁定到變量時當即計算,而是在求值程序須要產生表達式的值時進行計算
你能夠理解爲流水線上每個節點都只是作了一系列的設置,並無馬上去計算數值
沒有反作用
反作用是指在運算過程當中,修改了函數內部局部變量之外的其餘變量的狀態,好比你修改了類成員變量
沒有反作用也就意味着不產生運算之外的其餘結果,不修改系統的變量
引用透明性
若是提供一樣的輸入,那麼函數老是返回一樣的結果
也就是說表達式的值不依賴於能夠改變值的全局狀態,好比不依賴成員變量的值
爲何要使用函數式編程?
關注作什麼 更接近於天然語言
任務分解後你的解題思路將是如何調用各個不一樣的函數,要作什麼
不在關注於函數內部的細節自己去思考怎麼作
假設有這麼一組Student學生類型的List數據,學生有性別男女 |
若是在Java代碼中,你會如何解題?
僞代碼:
List<Student> 男List ;
for(int i=0;i<studentList.length;i++){//studentList 爲學生列表,其中有男有女
if(studentList[i].性別 == 男){
男List.add(List[i])
}
你循環遍歷列表,找到符合條件的學生,而後把他加入另一個列表,這多是一種常見的解題思路
假設有個Student 學生表,每條記錄都有一個性別字段值爲男女 |
若是是在數據庫中查詢呢,一種可能的解法是這樣子的
select
*
from
student
where
sex='男';
他們的主要區別是什麼?
一個最直觀的差異就是:
java代碼中是你本身去循環數據項,你本身處理每一項數據,找出符合你要求的數據
SQL查詢中,你只是傳入通知條件where sex='男'; ,數據庫在本身內部進行了循環,幫咱們找出來符合要求的數據
這就是外部循環和內部循環,這是一種思惟方式的轉變
外部循環,須要程序員本身去關注每個數據項
內部循環,程序員只須要關注結果
內部循環以及函數調用 也將咱們從如何作中解放出來,讓咱們再也不關注數據項循環的細節自己,僅僅關注於這次調用的結果
無論是什麼方式進行思考編程,你都會將你的任務進行分解
劃分爲更小的子任務
可是不一樣的是:
在如何作的思惟下,你還須要思考在每一個子任務中,每個細節是怎麼處理的,好比循環中進行條件判斷
這其實仍是往計算機的思惟傾斜的一種思考方式,這是指令式或者命令式的編程模式
在作什麼的思惟下,你不在關注每一個子任務的內部細節,只在意結果也就是"作什麼"
每一個子任務內部的細節是函數本身內部的事情,這更加符合人的思惟習慣
內部循環不也是函數式編程的一種表現形式麼
函數自己如同一個黑盒通常,有輸入有輸出,咱們不關心內部的實現細節,僅僅在意輸入和輸出
內部循環也是如此,咱們告訴他咱們想要的結果行爲,他返回給咱們結果
好比SQL中
where sex='男'; 這就是對咱們行爲的描述(不要把它理解成篩選條件)
咱們將行爲像參數同樣傳遞給了數據庫軟件,數據庫執行查詢操做,根據的是咱們給定的行爲
這就是行爲參數化的魅力所在
行爲參數化也是一種思惟模式,只要能把行爲像參數同樣進行傳遞 就是行爲參數化
有人可能已經想到了匿名內部類
new Thread(new Runnable() {
@Override
public void run() {
System.out.println();
}
}).start();
的確這也是一種行爲參數化,可是顯然,這段代碼還不夠簡潔純粹,由於方法的外層還套了一層對象
Java8中的行爲參數化,傳遞的將是更加純粹的行爲,而再也不須要藉助一個匿名對象的形式,並且,Lambda表達式不會像內部類同樣生成一個類
傳遞的是方法自己,方法中的代碼自己
那麼行爲參數化,不也就是函數式編程中的閉包特性麼
更加易於併發編程
函數式編程的準則是沒有反作用不依賴外部的數據,也不改變外部數據的值。
咱們知道線程安全的根本在於共享數據,若是沒有任何的數據共享,那麼不少的併發/線程安全問題都將迎刃而解
因此說這一特性正好知足了多核並行程序設計的需求,因此很顯然可以簡化並行程序的開發
函數式編程代碼簡潔
函數式編程大量使用函數,減小了代碼的重複,就如同你調用別人的方法同樣不是麼,一行就獲得告終果
Java8 對於函數式編程的支持
編程語言把函數式編程的概念引入,也就是使自身支持函數式編程的特性,換句話說也就是
在語言內部可使用一系列的類型或者關鍵字或者符號組合等進行表示
Java主要涉及這三個核心概念
- 函數接口(FunctionalInterface)
- 流(Stream)
- 收集器(Collector)
函數接口
既然函數式編程要求函數能夠是同值同樣的一等公民用於參數化傳遞,那麼必需要有表示函數的類型
先說一下函數式接口的註解
註解@FunctionalInterface 描述了什麼是一個函數式接口
public @interface FunctionalInterface {}
上面的註釋也就至關因而函數式接口的定義:
一個函數式接口只能有一個抽象方法,default方法有實現,因此不是抽象方法
若是一個接口聲明瞭一個覆蓋Object public公有方法的抽象方法,也不算是抽象方法
因此說:函數式接口,有且僅有一個抽象方法,覆蓋Object的public方法不計算在內(若是是覆蓋Object的protected那麼會計數的)
好比
java.lang.Runnable、java.util.Comparator是典型的函數式接口
接口上定義了函數的類型參數
抽象方法的方法簽名限定了函數(函數式接口的抽象方法的簽名稱爲函數描述符)
因此說一個函數接口,只能描述一種類型的函數
好比
Function<T, R> 這個函數接口
他表示形如
R function(T){
....
return R
}
他的類型參數是T R,調用方法apply 輸入爲T 輸出爲R
做用爲轉換一個對象爲不一樣類型的對象
全部這種形式的函數都是這個函數接口類型
好比
public static void main(String[] args){
Function<String,Boolean> function = (String x)->x.equals("true");
System.out.println(function.apply("1"));
System.out.println(function.apply("true"));
}
至此,Java中已經有了用於表示函數的類型了,也就是能夠定義一個函數或者返回一個函數,或者把函數當作一個參數值進行傳遞了
以賦值運算符的形式來類比的話就是
好比
int i = 1;
等號左邊的類型已經有了就是函數接口
可是右邊,也就是行爲參數化這個行爲到底如何表示呢?也便是上面的1 的位置
在java中可使用
-
Lambda表達式((String x)->x.equals("true"))
-
方法引用(String::length)
兩種形式進行表示
好比
public static void main(String[] args){
Function<String,Integer> function = String::length;
System.out.println(function.apply("1"));
System.out.println(function.apply("true"));
}
既然每一種函數類型都須要存在指定形式的函數接口,想要使用Lambda-匿名函數或者方法引用,天然須要定義函數接口
函數類型的說法可能不太準確,函數式接口的抽象方法的簽名稱爲函數描述符 其實說的也都仍是方法簽名 方法簽名惟一的標識了一個函數
Java8 也已經給咱們預置了一些經常使用的函數接口類型
已經定義一套可以描述常見函數描述符的函數接口
好比上面提到的
function 就是其中一種
另外還有其餘一些,後面再說,咱們已經能夠在Java中表示一個函數,而且對函數進行調用
流
流,流動,流水,java中早就已經有了IO流,形象的表達了數據在程序中的處理與流動
Java8中的Stream流則更傾向於流水線的含義
每一個節點有各自獨立的功能目的,根據你的目的(作什麼),將各個獨立的功能目的節點拼接成一整個的完整的流水線
數據在此流水線上進行加工處理,最終得出結果
經過告知Stream "作什麼" 來進行數據操做和處理
你不在須要關注內部的細節,Stream經過內部迭代進行數據項的篩選查找,找到符合條件的數據
流(Stream)是Java8對函數式編程的重要支撐。大部分函數式工具都圍繞Stream展開
也能夠說Stream類是Java8 關於函數式編程定義的一些列函數集合
由此能夠看得出來,Stream的重要性
想要使用Java進行函數式編程,僅僅使用Lambda表達式是不夠的,必須有足夠的函數,Lambda表達式只有跟stream一塊兒使用才能顯示其真實的威力
集合是一種數據結構用於存儲數據
Stream不是一種數據結構,是對於數據的一種新的視圖,用於數據的計算,提供了一系列的API用於調用
|
歸納的說
Stream就是函數式編程中編程語言提供出來的庫方法集合,而參數基本上都是函數
因此才說,Lambda表達式只有跟stream一塊兒使用才能顯示其真實的威力
經常使用的Stream調用流程
1.得到Stream
想要使用Stream的一些特性,顯然你必須把你的數據集轉換生成爲Stream,這沒有Stream何談使用?
2.設置行爲類型 也就是操做類型
這句話有些模糊不清,其實就是你須要設置想幹什麼
究竟是篩選數據?轉換數據?求和仍是怎樣?
你能夠類比爲SQL查詢中究竟是SELECT 仍是UPDATE 或者DELETE? 這就是行爲的類型
爲了更快理解的話,你能夠片面的理解爲調用Stream類的方法
咱們舉例說明
好比你常常讓同窗幫你買東西
買東西就是行爲類型,是去買東西,既不是幫你開車也不是陪你看電影
這就是行爲的類型
Stream中有一系列的API能夠幫助咱們達到這個目的
好比 filter map等等
3. 肯定行爲參數 也就是操做內容
行爲參數也就是基於已經設置的行爲類型下,你具體要以什麼樣子的行爲去執行
你篩選數據篩選什麼樣子的數據?
轉換數據,轉換爲何形式?
類比爲SQL查詢中就是查詢條件,查詢 男生?查詢 女生? 這就是行爲的具體方式
仍是剛纔的例子,你常常讓同窗幫你買東西,那到底買什麼?買礦泉水仍是買麪包?這就是肯定行爲參數
Java8中使用方法引用或者Lambda-匿名函數 或者方法引用來表示行爲參數
4.行爲的屬性
既然是流水線式的工做方式,那麼當前的工做結束後或許結束了或許是進入到流水線的下一環節
固然最終他確定仍是會結束掉的
這就又涉及到綁定行爲方法的屬性種類 究竟是中間的操做(能夠繼續傳遞給流水線下一步) 仍是結束的終端操做
中間操做的返回結果仍是一個Stream 你仍舊能夠對他進行上述相似的過程
終端操做則通常會將流進行收集整理成指定的數據結構
這基本上是一個經常使用的Stream使用流程
流程處理雖然很簡單,可是強大之處在於中間操做處理後仍舊是流
這就意味着你能夠按照須要進行無數的變換組合以達到你想要的效果
收集器
Stream結合Lambda表達式能夠對於數據進行各類各樣的操做
可是Stream 終歸是Stream ,它並非一種數據結構,無論通過了多少處理,他終歸是再次返回到代碼中具體的其餘數據類型中
把Stream類比作數據項處理的流水線的話
中間操做就是流水線上的一個個的功能操做節點
而收集器就是在某些結束操做中用於將數據進行轉換的工具
在Java中關於收集器有幾個關鍵的概念
1. Stream中的collect 方法是收集器的調用者
<R, A> R collect(Collector<? super T, A, R> collector);
2. Collector 接口 定義了收集器
public interface Collector<T, A, R> {
3. 收集器工廠Collectors 用於預置一些收集器
public final class Collectors
好比 .....collect(Collectors.toList()); 就是把一個處理後的流轉換爲List
總結:
Java8 構建了三個主要概念,函數接口,流,收集器
有了函數接口 函數擁有了類型也就是能夠像值同樣做爲參數進行傳遞,做爲返回值,或者使用變量進行表示
使用Lambda-匿名函數或者方法引用來表示行爲參數 也就是函數的值
Stream是Java8 提供的函數式編程的"庫函數" 預約了一些經常使用的操做模式,經過Lambda表達式結合使用
收集器用於把Stream處理後的數據進行打包整理成你須要的數據結構