Java 函數式編程

從歷史上看,用 Java 進行函數式編程并不容易,甚至一些函數式編程在 Java 中是不可能實現的。在 Java 8 , Oracle 做了一些努力使得函數式編程變得更容易,這些努力在某種程度上取得了成功。在這個 Java 函數式編程教程中將介紹基本的函數式編程,以及 Java 中可以實現的部分。

函數式編程基礎

函數式編程包括以下幾個關鍵的概念:

  • 函數是第一類對象
  • 純函數
  • 高階函數

純函數編程也有一套規則需要遵守:

  • 無狀態
  • 無副作用
  • 不可變變量
  • 優先使用遞歸而不是循環

這些概念和規則將會在這篇教程的剩余部分一一介紹。

盡管如果你一直沒有遵守這些規則,你同樣可以從函數式編程中獲取收獲并應用在你的應用中。你將會看到函數式編程并不是解決所有問題的正確工具,特別是 “無副作用” 使得寫入數據庫(這是個副作用)變得很困難。你需要了解哪些問題函數式編程擅長解決,哪些不是。

函數是第一類對象

在函數式編程范式中,函數是語言里第一類對象。這意味著你可以創建一個函數的 “實例”,作為一個函數實例的引用,就像 String、 Map 和其它對象的引用一樣。函數同樣可以當做參數傳遞給其它函數。
在 Java 里,方法 不是第一類對象。 最接近的是 Java Lamda 表達式,在這里我不討論這個。

純函數

一個函數如果是一個 純函數,需要滿足以下條件:

  • 函數的執行不產生副作用
  • 函數的返回值只依賴入參

下面是一個 Java 里的 純函數 例子:

public class ObjectWithPureFunction{

    public int sum(int a, int b) {
        return a + b;
    }
}

注意 sum() 函數返回值只依賴入參。同樣注意下 sum() 函數沒有副作用,意味著它不改變當前函數以外任何狀態(變量)。

肯定的是,下面這是不是一個 純函數:

public class ObjectWithNonPureFunction{
    private int value = 0;

    public int add(int nextValue) {
        this.value += nextValue;
        return this.value;
    }
}

注意 add() 方法用了一個成員變量來計算它的返回值,而且它也修改改了 value 成員變量的值,因此他有副作用。

高階函數

一個函數如果是高階函數則至少要滿足以下條件之一:

  • 使用一個或多個函數作為參數
  • 函數返回另外一個函數作為結果

在 Java 中,最接近高階函數的是:一個函數(方法)使用一個或多個 lambda 表達式作為參數,以及返回另外一個 lambda 表達式。下面就是一個 Java 里 高階函數 的例子:

public class HigherOrderFunctionClass {

    public <T> IFactory<T> createFactory(IProducer<T> producer, IConfigurator<T> configurator) {
        return () -> {
           T instance = producer.produce();
           configurator.configure(instance);
           return instance;
        }
    }
}

注意 :1. createFactory() 方法返回一個 lambda 表達式作為返回值。這是作為 函數式編程的一個條件。
2. createFactory() 方法使用兩個接口(IProducer and Iconfigurator)的實現的實例作為參數. Java lambda 表達式需要實現一個 函數式接口,記得嗎?

想象一下接口是這樣:

public interface IFactory<T> {
   T create();
}
public interface IProducer<T> {
   T produce();
}
public interface IConfigurator<T> {
   void configure(T t);
}

你可以看到,這些接口都是函數式接口。因此他們可以被 Java lamda 表達式所實現 —— 所以 createFactory 方法是一個 高階函數

無狀態

像在教程開頭提到的,函數式編程范式的規則之一就是無狀態。 "無狀態" 通常指函數之外沒有狀態。一個函數可以擁有包含內部臨時狀態的本地變量,但是這個函數不能引用任何該函數所屬類或對象的成員變量。

下面是一個沒有使用外部狀態的例子:

public class Calculator {
    public int sum(int a, int b) {
       return a + b;
    }
}

相反, 下面是一個使用外部狀態的例子

public class Calculator {
    private int initVal = 5;
    public int sum(int a) {
       return initVal + a;
    }
}

這個函數違背了 "無狀態" 規則

無副作用

函數式范式另外一個規則就是 "無副作用"。這意味著,一個函數不能修改任何函數之外的狀態。改變函數之外的狀態被稱作一個 副作用
函數外部狀態有:函數所屬類或對象的成員變量,函數參數中的成員變量,或者外部系統的狀態比如文件系統或者數據庫。

不可變變量(Immutable Variables)

函數式編程范式的第三個規則是 “不可變變量”。不可變變量有利于防止產生副作用。

優先使用遞歸而不是循環

函數式編程范式的第四個規則是“優先使用遞歸而不是循環”。遞歸使用函數調用來達到循環的目的,因此代碼變得更函數式。
循環的另一個代替是 Java Stream API. 這個 API 是受 函數式啟發的。

函數式接口

函數式接口在 Java 里是只擁有一個抽象方法的接口。一個抽象方法意味著只有一個方法沒有實現。一個接口可以有多個方法比如默認方法和靜態方法——兩個都有實現,但只要這個接口只有一個沒有實現的接口,這個接口就叫做 函數式接口
下面是一個函數式的例子:

public interface MyInterface {
    public void run();
}

下面是另外一個攜帶默認方法和靜態方法實現的函數式接口:

public interface MyInterface2 {
    public void run();

    public default void doIt() {
        System.out.println("doing it");
    }

    public static void doItStatically() {
        System.out.println("doing it statically");
    }
}

注意這兩個擁有實現的方法。這仍是一個 函數接口,因為只有 run() 方法沒有被實現。可是如果再有一個沒有實現的方法,這個接口就不再是函數式接口,不能被 Java lambda 表達式所實現。
譯自: Java Functional Programming

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容