之前寫過一篇java8的流操做,人們都說流操做是函數式編程,但函數式編程是什麼呢?html
什麼是函數式編程?它是一種編程範式,即一切都是數學函數。函數式編程語言裏也能夠有對象,但一般這些對象都是恆定不變的 —— 要麼是函數參數,要什麼是函數返回值。函數式編程語言裏沒有 for/next 循環,由於這些邏輯意味着有狀態的改變。相替代的是,這種循環邏輯在函數式編程語言裏是經過遞歸、把函數當成參數傳遞的方式實現的。java
函數式編程單看上面的理論無疑是沒法理解的,因此,須要經過它的一些特色來感覺什麼是函數式編程git
所謂"第一等公民"(first class),指的是函數與其餘數據類型同樣,處於平等地位,能夠賦值給其餘變量,也能夠做爲參數,傳入另外一個函數,或者做爲別的函數的返回值。
舉例來講,下面代碼中的print變量就是一個函數,能夠做爲另外一個函數的參數。程序員
"表達式"(expression)是一個單純的運算過程,老是有返回值;"語句"(statement)是執行某種操做,沒有返回值。函數式編程要求,只使用表達式,不使用語句。也就是說,每一步都是單純的運算,並且都有返回值。
github
緣由是函數式編程的開發動機,一開始就是爲了處理運算(computation),不考慮系統的讀寫(I/O)。
"語句"屬於對系統的讀寫操做,因此就被排斥在外。spring
固然,實際應用中,不作I/O是不可能的。所以,編程過程當中,函數式編程只要求把I/O限制到最小,不要有沒必要要的讀寫行爲,保持計算過程的單純性。express
所謂"反作用"(side effect),指的是函數內部與外部互動(最典型的狀況,就是修改全局變量的值),產生運算之外的其餘結果。編程
函數式編程強調沒有"反作用",意味着函數要保持獨立,全部功能就是返回一個新的值,沒有其餘行爲,尤爲是不得修改外部變量的值.
segmentfault
上一點已經提到,函數式編程只是返回新的值,不修改系統變量
。所以,不修改變量,也是它的一個重要特色。app
在其餘類型的語言中,變量每每用來保存"狀態"(state)。不修改變量,意味着狀態不能保存在變量中。函數式編程使用參數保存狀態,最好的例子就是遞歸。
引用透明(Referential transparency),指的是函數的運行不依賴於外部變量或"狀態",只依賴於輸入的參數,任什麼時候候只要參數相同,引用函數所獲得的返回值老是相同的。
OO(object oriented,面向對象)是抽象數據,FP(functional programming,函數式編程)是抽象行爲。
在java中,函數式編程是經過lambda表達式
實現的。
基本表達形式以下:
Runnable noArguments = () -> System.out.println("Hello World");
Lambda 表達式的基本語法是:
->
,可視爲「產出」。->
以後的內容都是方法體。
- 當只用一個參數,能夠不須要括號
()
。 然而,這是一個特例。- 正常狀況使用括號
()
包裹參數。 爲了保持一致性,也可使用括號()
包裹單個參數,雖然這種狀況並不常見。- 若是沒有參數,則必須使用括號
()
表示空參數列表。- 對於多個參數,將參數列表放在括號
()
中。- 方法體的語句超過一句時,須要使用
{}
,並根據狀況看是否須要return
Lambda 表達式產生函數,而不是類。 在 JVM(Java Virtual Machine,Java 虛擬機)上,一切都是一個類,所以在幕後執行各類操做使 Lambda 看起來像函數 —— 但做爲程序員,你能夠高興地僞裝它們「只是函數」。
/** 強制 javac 檢查一個接口是否符合函數接口的標準。若是該註釋添加給一個枚舉類型、類或另外一個註釋,或者接口包含不止一個抽象方法,javac 就會報錯 **/ @FunctionalInterface //該註解不是必選項 interface Yunzhi { //只包含一個抽象方法,稱爲函數式方法。 String test(String s); } public class LambdaExpressions { static Yunzhi yunzhi = x ->"this is a" + x ; public static void main(String[] args) { System.out.println(yunzhi.test("test")); } }
yunzhi
其實是實現了Yunzhi
的test()
,相似上面的這種接口叫函數式接口。
JDK 8 中提供了大量的函數接口,這些接口定義在java.util.function
中,所以咱們通常狀況下不需再定義本身的接口,同時,各個接口的做用和名字都是相對應的,因此,瞭解函數式接口的命名模式就是頗有必要的了。
如下是基本命名準則:
- 若是隻處理對象而非基本類型,名稱則爲
Function
,Consumer
,Predicate
等。參數類型經過泛型添加。- 若是接收的參數是基本類型,則由名稱的第一部分表示,如
LongConsumer
,DoubleFunction
,IntPredicate
等,但基本Supplier
類型例外。- 若是返回值爲基本類型,則用
To
表示,如ToLongFunction <T>
和IntToLongFunction
。- 若是返回值類型與參數類型一致,則是一個運算符:單個參數使用
UnaryOperator
,兩個參數使用BinaryOperator
。- 若是接收兩個參數且返回值爲布爾值,則是一個謂詞(Predicate)。
- 若是接收的兩個參數類型不一樣,則名稱中有一個
Bi
。
下表描述了 java.util.function 中的目標類型(包括例外狀況):
特徵 | 數式方法名 | 示例 |
---|---|---|
無參數; 無返回值 |
Runnable (java.lang) run() |
Runnable |
無參數; 返回類型任意 |
Supplier get() getAs類型() |
Supplier<T> BooleanSupplier IntSupplier LongSupplier DoubleSupplier |
無參數; 返回類型任意 |
Callable (java.util.concurrent) call() |
Callable<V> |
1 參數; 無返回值 |
Consumer accept() |
Consumer<T> IntConsumer LongConsumer DoubleConsumer |
2 參數 Consumer | BiConsumer accept() |
BiConsumer<T,U> |
2 參數 Consumer; 1 引用; 1 基本類型 |
Obj類型Consumer accept() |
ObjIntConsumer<T> ObjLongConsumer<T> ObjDoubleConsumer<T> |
1 參數; 返回類型不一樣 |
Function apply() To類型 和 類型To類型 applyAs類型() |
Function<T,R> IntFunction <R> LongFunction<R> DoubleFunction <R> ToIntFunction <T> ToLongFunction<T> ToDoubleFunction<T> IntToLongFunction IntToDoubleFunction LongToIntFunction LongToDoubleFunction DoubleToIntFunction DoubleToLongFunction |
1 參數; 返回類型相同 |
UnaryOperator apply() |
UnaryOperator<T> IntUnaryOperator LongUnaryOperator DoubleUnaryOperator |
2 參數類型相同; 返回類型相同 |
BinaryOperator apply() |
BinaryOperator<T> IntBinaryOperator LongBinaryOperator DoubleBinaryOperator |
2 參數類型相同; 返回整型 |
Comparator (java.util) compare() |
Comparator<T> |
2 參數; 返回布爾型 |
Predicate test() |
Predicate<T> BiPredicate<T,U> IntPredicate LongPredicate DoublePredicate |
參數基本類型; 返回基本類型 |
類型To類型Function applyAs類型() |
IntToLongFunction IntToDoubleFunction LongToIntFunction LongToDoubleFunction DoubleToIntFunction DoubleToLongFunction |
2 參數類型不一樣 | Bi操做 (不一樣方法名) |
BiFunction<T,U,R> BiConsumer<T,U> BiPredicate<T,U> ToIntBiFunction<T,U> ToLongBiFunction<T,U> ToDoubleBiFunction<T> |
其中最經常使用的是:
接下來實際的看看如何使用這些接口
class Foo {} class Bar { Foo f; Bar(Foo f) { this.f = f; } } public class FunctionVariants { static Function<Foo, Bar> f1 = f -> new Bar(f); public static void main(String[] args) { Bar b = f1.apply(new Foo()); } }
學到這裏才忽然發現,雖然之前徹底不知道函數接口這個東西,但實際上卻已經在使用他了,好比Comparator
普遍的運用在各類比較當中,pridect
也在spring的綜合查詢中使用過了。
雖然在宣傳中,函數式編程有着巨大的優點,好比適合並行編程
、代碼可靠性
和代碼建立和庫複用
,但在某些大佬看來:
關於函數式編程能高效建立更健壯的代碼這一觀點仍存在部分爭議。雖然已有一些好的範例,但還不足以證實純函數式語言就是解決編程問題的最佳方法。
無論怎樣,多學一學老是好的