重識Java8函數式編程

前言

最近真的是太忙忙忙忙忙了,好久沒有更新文章了。最近工做中看到了幾段關於函數式編程的代碼,可是有點費解,因而就準備總結一下函數式編程。不少東西很簡單,可是若是不總結,可能會被它的各類變體所困擾。接觸Lambda表達式已經好久了,可是也一直是處於照葫蘆畫瓢的階段,因此想本身去編寫相關代碼,也有些捉襟見肘。java

1. Lambda表達式的不一樣形式

// 基本形式
參數 -> 主體

1.1 形式一

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

該形式的Lambda表達式不包含參數,使用空括號()表示沒有參數。它實現了Runnable接口,該接口也只有一個run方法,沒有桉樹,且返回類型爲void。程序員

1.2 形式二

ActionListener oneArgument = event -> System.out.println("button clicked");

該形式的Lambda表達式包含且只包含一個參數,可省略參數的符號。編程

1.3 形式三

Runnable multiStatement = () -> {
	System.out.print("Hello"); 
    System.out.println(" World"); 
};

Lambda表達式的主體不只可使一個表達式,並且也能夠是一段代碼塊,使用大括號{}將代碼塊括起來。該代碼塊和普通方法遵循的規則別無二致,能夠用返回或拋出異常來退出。只有以行代碼的Lambda表達式也可使用大括號,用以明確Lambda表達式從何處開始,到哪裏結束。編程語言

1.4 形式四

BinaryOperator<Long> add = (x, y) -> x + y;

Lambda表達式也能夠表示包含多個參數的方法,上面的Lambda表達式並非將兩個數字相加,而是建立了一個函數,用來計算兩個數字相加的結果。變量add的類型時BinaryOperator ,它不是兩個數字的和,而是將兩個數字相加的那行代碼。 函數式編程

1.5 形式五

BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;

到目前爲止,全部Lambda表達式中的參數類型都是由編譯器推斷得出的。但有時最好也能夠顯示聲明參數類型,此時就須要使用小括號將參數括起來,多個參數的狀況也是如此。函數

2. 引用值,而不是變量

若是你曾使用過匿名內部類,也許遇到過這樣的狀況:須要引用它所在方法裏的變量。這是,須要將變量聲明爲final。設計

final String name = getUserName(); 
button.addActionListener(new ActionListener() {
	public void actionPerformed(ActionEvent event) { 
        System.out.println("hi " + name); 
    } 
});

將變量聲明爲 final,意味着不能爲其重複賦 值。同時也意味着在使用 final 變量時,其實是在使用賦給該變量的一個特定的值。code

Java 8 雖然放鬆了這一限制,能夠引用非 final 變量,可是該變量在既成事實上必須是 final(意思就是你不能再次對該變量賦值)。雖然無需將變量聲明爲 final,但在 Lambda 表達式中,也沒法用做非終態變量。如 果堅持用做非終態變量,編譯器就會報錯。 既成事實上的 final 是指只能給該變量賦值一次。換句話說,Lambda 表達式引用的是值, 而不是變量。orm

例如:對象

String name = getUserName(); 
button.addActionListener(event -> System.out.println("hi " + name));

3. 函數接口

在 Java 裏,全部方法參數都有固定的類型。假設將數字 3 做爲參數傳給一個方法,則參數 的類型是 int。那麼,Lambda 表達式的類型又是什麼呢?

使用只有一個方法的接口來表示某特定方法並反覆使用,是很早就有的習慣。使用 Swing 編寫過用戶界面的人對這種方式都不陌生,這裏無需再標新立異,Lambda 表達式也使用一樣的技巧,並將這種接口稱爲函數接口。

接口中單一方法的命名並不重要,只要方法簽名和 Lambda 表達式的類型匹配便可。可在函數接口中爲參數起一個有意義的名字,增長代碼易讀性,便於更透徹 地理解參數的用途。

3.1 Java中重要的函數接口

接口 參數 返回類型 示例
Predicate T boolean 判斷是否
Consumer T void 輸出一個值
Function<T,R> T T 得到對象的名字
Supplier None T 工廠方法
UnaryOperator T T 邏輯非(!)
BinaryOperator (T, T) T 求兩個數的乘積(*)

3.2 函數接口定義

定義函數接口須要使用到註解@FunctionalInterface

例如:

@FunctionalInterface
public interface MyFuncInterface {
	void print();
}

使用:

public class MyFunctionalInterfaceTest {
    public static void main(String[] args) {
        doPrint(() -> System.out.println("java"));
    }

    public static void doPrint(MyFuncInterface my) {
        System.out.println("請問你喜歡什麼編程語言?");
        my.print();
    }
}

說明:

這只是一個很簡單的例子,有人以爲爲何要搞這麼複雜,去定義一個接口?這個問題仍是讀者在平時的工做中去感悟吧,總之,先學會怎麼用它。不至於看了別人寫的代碼都看不懂。

至於我我的的理解,能夠簡單聊聊。之前寫過JavaScript,裏面有一種語法就是將自定義函數B做爲參數傳遞到另一個函數A裏面,在函數A裏面會執行你自定義的函數B邏輯,我當時就很是喜歡這種特性,由於每一個人關於函數B的實現可能不同,亦或者場景不同也會致使函數B的實現不同。我以爲Java8的這個函數式編程就是對這一特性的補充。

4. 流

流的經常使用操做有不少,例如collect(toList())mapfiltermaxmin等,下面介紹一下flatMapreduce

4.1 flatMap

flatMap 方法可用 Stream 替換值,而後將多個 Stream 鏈接成一個 Stream。

List<Integer> together = Stream.of(asList(1, 2), asList(3, 4)) 				 
    .flatMap(numbers -> numbers.stream())
    .collect(toList()); 
assertEquals(asList(1, 2, 3, 4), together);

調用 stream 方法,將每一個列表轉換成 Stream 對象,其他部分由 flatMap 方法處理。 flatMap 方法的相關函數接口和 map 方法的同樣,都是 Function 接口,只是方法的返回值 限定爲 Stream 類型罷了。

4.2 reduce

reduce 操做能夠實現從一組值中生成一個值。對於 count、min 和 max 方 法,由於經常使用而被歸入標準庫中。事實上,這些方法都是 reduce 操做。

如何經過 reduce 操做對 Stream 中的數字求和。以 0 做起點——一個空Stream 的求和結果,每一步都將 Stream 中的元素累加至 accumulator,遍歷至 Stream 中的 最後一個元素時,accumulator 的值就是全部元素的和。

int count = Stream.of(1, 2, 3)
    .reduce(0, (acc, element) -> acc + element); 
assertEquals(6, count);

Lambda 表達式的返回值是最新的 acc,是上一輪 acc 的值和當前元素相加的結果。reducer 的類型是前面已介紹過的 BinaryOperator。

5. Optional

reduce 方法的一個重點還沒有說起:reduce 方法有兩種形式,一種如前面出現的須要有一 個初始值,另外一種變式則不須要有初始值。沒有初始值的狀況下,reduce 的第一步使用 Stream 中的前兩個元素。有時,reduce 操做不存在有意義的初始值,這樣作就是有意義的,此時,reduce 方法返回一個 Optional 對象。

Optional 是爲核心類庫新設計的一個數據類型,用來替換 null 值。人們對原有的 null 值有不少抱怨。人們經常使用 null 值表示值不存在,Optional 對象能更好地表達這個概念。使用 null 代 表值不存在的最大問題在於 NullPointerException。一旦引用一個存儲 null 值的變量,程 序會當即崩潰。使用 Optional 對象有兩個目的:首先,Optional 對象鼓勵程序員適時檢查變量是否爲空,以免代碼缺陷;其次,它將一個類的 API 中可能爲空的值文檔化,這比閱讀實現代碼要簡單不少。

下面咱們舉例說明 Optional 對象的 API,從而切身體會一下它的使用方法。使用工廠方法 of,能夠從某個值建立出一個 Optional 對象。Optional 對象至關於值的容器,而該值能夠 經過 get 方法提取。

Optional<String> a = Optional.of("a"); 
assertEquals("a", a.get());

Optional 對象也可能爲空,所以還有一個對應的工廠方法 empty,另一個工廠方法 ofNullable 則可將一個空值轉換成 Optional 對象。下面的代碼同時展現 了第三個方法 isPresent 的用法(該方法表示一個 Optional 對象裏是否有值)。

Optional emptyOptional = Optional.empty(); 
Optional alsoEmpty = Optional.ofNullable(null); assertFalse(emptyOptional.isPresent());

使用 Optional 對象的方式之一是在調用 get() 方法前,先使用 isPresent 檢查 Optional 對象是否有值。使用 orElse 方法則更簡潔,當 Optional 對象爲空時,該方法提供了一個 備選值。若是計算備選值在計算上太過繁瑣,便可使用 orElseGet 方法。該方法接受一個 Supplier 對象,只有在 Optional 對象真正爲空時纔會調用。

assertEquals("b", emptyOptional.orElse("b")); 
assertEquals("c", emptyOptional.orElseGet(() -> "c"));

最後

實踐是檢驗真理的惟一標準,多寫代碼,多思考,你的代碼纔會愈來愈好。
end
Java開發樂園

相關文章
相關標籤/搜索