java函數式編程入門

之前寫過一篇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表達式實現的。

lambad表達式

基本表達形式以下:

Runnable noArguments = () -> System.out.println("Hello World");

Lambda 表達式的基本語法是:

  1. 參數。(能夠有多個)
  2. 接着 ->,可視爲「產出」。
  3. -> 以後的內容都是方法體。
  • 當只用一個參數,能夠不須要括號()。 然而,這是一個特例。
  • 正常狀況使用括號()包裹參數。 爲了保持一致性,也可使用括號()包裹單個參數,雖然這種狀況並不常見。
  • 若是沒有參數,則必須使用括號()表示空參數列表。
  • 對於多個參數,將參數列表放在括號()中。
  • 方法體的語句超過一句時,須要使用{},並根據狀況看是否須要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其實是實現了Yunzhitest(),相似上面的這種接口叫函數式接口。

JDK 8 中提供了大量的函數接口,這些接口定義在java.util.function中,所以咱們通常狀況下不需再定義本身的接口,同時,各個接口的做用和名字都是相對應的,因此,瞭解函數式接口的命名模式就是頗有必要的了。

如下是基本命名準則:

  1. 若是隻處理對象而非基本類型,名稱則爲FunctionConsumerPredicate等。參數類型經過泛型添加。
  2. 若是接收的參數是基本類型,則由名稱的第一部分表示,如LongConsumerDoubleFunctionIntPredicate等,但基本Supplier類型例外。
  3. 若是返回值爲基本類型,則用To表示,如ToLongFunction <T>IntToLongFunction
  4. 若是返回值類型與參數類型一致,則是一個運算符:單個參數使用UnaryOperator,兩個參數使用BinaryOperator
  5. 若是接收兩個參數且返回值爲布爾值,則是一個謂詞(Predicate)。
  6. 若是接收的兩個參數類型不一樣,則名稱中有一個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>

其中最經常使用的是:

image.png

接下來實際的看看如何使用這些接口

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的綜合查詢中使用過了。

關於函數式編程的爭議

雖然在宣傳中,函數式編程有着巨大的優點,好比適合並行編程代碼可靠性代碼建立和庫複用,但在某些大佬看來:

關於函數式編程能高效建立更健壯的代碼這一觀點仍存在部分爭議。雖然已有一些好的範例,但還不足以證實純函數式語言就是解決編程問題的最佳方法。

無論怎樣,多學一學老是好的

參考文章

函數式編程初探
Java 函數式編程(一)初識篇
函數式編程
java8的函數式編程
JDK 8 函數式編程入門

相關文章
相關標籤/搜索