Java 8 函數式編程:Lambda 表達式和方法引用

背景

在不少其餘語言中,函數是一等公民。例如 JavaScript 中,函數(Function)和字符串(String)、數字(Number)、對象(Object)等同樣是一種數據類型。能夠這樣定義函數:javascript

var myFunction = function () {
    doSomething();
};

也能夠將函數做爲參數:html

setTimeout(function() { 
    doSomething(); 
}, 1000);

在 Java 中,函數不是一等公民。若是想要像其餘語言同樣定義一個函數,只能經過定義一個接口來實現,例如 Runnablejava

在 Java 8 以前,能夠經過匿名類的方式來建立 Runnableweb

Thread thread = new Thread(new Runnable() {
    public void run() {
        doSomethong();
    }
});
thread.start();

Java 8 中能夠經過 lambda 表達式來建立:express

Thread thread = new Thread(() -> doSomethong());
thread.start();

也就是:segmentfault

Runnable runnable = new Runnable() {
    public void run() {
        doSomethong();
    }
};

簡化成了:api

Runnable runnable = () -> doSomethong();

是否是看起來像 JavaScript 的函數定義:oracle

var myFunction = function () {
    doSomething();
};

@FunctionalInterface

An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification. Conceptually, a functional interface has exactly one abstract method.

@FunctionalInterface 註解用於代表一個接口是函數式接口(functional interface)。函數式接口必須有且只有一個抽象方法。less

例如 java.lang.Runnable 就是一個函數式接口,有且僅有一個抽象方法 runRunnable 源碼中就有加上註解 @FunctionalInterface函數

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.

函數式接口實例能夠經過 lambda 表達式、方法引用(method reference)、構造方法引用(constructor reference)的方式來建立。

However, the compiler will treat any interface meeting the definition of a functional interface as a functional interface regardless of whether or not a FunctionalInterface annotation is present on the interface declaration.

編譯器會把知足函數式接口定義(有且只有一個抽象方法)的任何接口視爲函數式接口 ,不管有沒有 @FunctionalInterface 註解。

以上兩條總結一下:當一個接口符合函數式接口定義(有且只有一個抽象方法),那麼就能夠經過 lambda 表達式、方法引用的方式來建立,不管該接口有沒有加上 @FunctionalInterface 註解。

下面列出一些 Java 中的函數式接口:

  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.util.Comparator
  • java.util.function 包下的函數式接口,例如 PredicateConsumerFunctionSupplier

java.util.function

java.util.function 包下,定義了大量的函數式接口,每一個接口都有且只有一個抽象方法,這些接口的區別在於其中的抽象方法的參數和返回值不一樣。

類型 參數個數 參數類型 返回值類型
Function<T,R> 1 T R
IntFunction<R> 1 int R
LongFunction<R> 1 long R
DoubleFunction<R> 1 double R
ToIntFunction<T> 1 T int
ToLongFunction<T> 1 T long
ToDoubleFunction<T> 1 T double
IntToLongFunction 1 int long
IntToDoubleFunction 1 int double
LongToIntFunction 1 long int
LongToDoubleFunction 1 long double
DoubleToIntFunction 1 double int
DoubleToLongFunction 1 double long
BiFunction<T,U,R> 2 T,U R
ToIntBiFunction<T,U> 2 T,U int
ToLongBiFunction<T,U> 2 T,U long
ToDoubleBiFunction<T,U> 2 T,U double
UnaryOperator<T> 1 T T
IntUnaryOperator 1 int int
LongUnaryOperator 1 long long
DoubleUnaryOperator 1 double double
BinaryOperator<T> 2 T,T T
IntBinaryOperator 2 int,int int
LongBinaryOperator 2 long,long long
DoubleBinaryOperator 2 double,double double
Consumer<T> 1 T void
IntConsumer 1 int void
LongConsumer 1 long void
DoubleConsumer 1 double void
BiConsumer<T,U> 2 T,U void
ObjIntConsumer<T> 2 T,int void
ObjLongConsumer<T> 2 T,long void
ObjDoubleConsumer<T> 2 T,double void
Supplier<T> 0 - T
BooleanSupplier 0 - boolean
IntSupplier 0 - int
LongSupplier 0 - long
DoubleSupplier 0 - double
Predicate<T> 1 T boolean
IntPredicate 1 int boolean
LongPredicate 1 long boolean
DoublePredicate 1 double boolean
BiPredicate<T,U> 2 T,U boolean

Lambda 表達式

One issue with anonymous classes is that if the implementation of your anonymous class is very simple, such as an interface that contains only one method, then the syntax of anonymous classes may seem unwieldy and unclear. In these cases, you're usually trying to pass functionality as an argument to another method, such as what action should be taken when someone clicks a button. Lambda expressions enable you to do this, to treat functionality as method argument, or code as data.

當一個接口中只有一個方法時(即知足函數式接口定義),此時經過匿名類的語法來編寫代碼顯得比較笨重。使用 lambda 表達式能夠將功能做爲參數,將代碼做爲數據。

一個 Lambda 表達式分爲如下三個部分:

  • Argument List : 參數列表
  • Arrow Token : 箭頭符號,即 ->
  • Body : 包含一個表達式或者一整塊代碼

下面舉幾個例子:

  1. 定義一個函數式接口對象,用於求兩個 int 之和,包含兩個 int 類型參數 xy,返回 x + y 的值:

    IntBinaryOperator sum = (x, y) -> x + y;
  2. 定義一個函數式接口對象,無參數,返回42:

    IntSupplier intSupplier = () -> 42;
  3. 定義一個函數式接口對象,用於輸出字符串,包含一個 String 類型的參數 s,無返回值:

    Consumer<String> stringConsumer = s -> {
        System.out.println(s);
    };

方法引用(Method Reference)

You use lambda expressions to create anonymous methods. Sometimes, however, a lambda expression does nothing but call an existing method. In those cases, it's often clearer to refer to the existing method by name. Method references enable you to do this; they are compact, easy-to-read lambda expressions for methods that already have a name.

若是 lambda 表達式只是調用一個已有的方法,那麼能夠直接使用方法引用。

例如輸出 List 中的元素,用 lambda 表達式:

List<String> list = Arrays.asList("1", "22", "333");
list.forEach(s -> System.out.println(s));

改用方法引用更加簡潔:

List<String> list = Arrays.asList("1", "22", "333");
list.forEach(System.out::println);

也就是:

Consumer<String> stringConsumer = s -> System.out.println(s);

簡化成了:

Consumer<String> stringConsumer = System.out::println; // 將一個已有的方法賦值給一個函數式接口對象

方法引用有如下幾種類型:

  1. 類名::靜態方法名 : 靜態方法引用

    例如定義一個 max 函數式接口對象,用於求兩個 int 中的最大值:

    IntBinaryOperator max = Math::max;

    IntBinaryOperator 表示有兩個 int 參數且返回值爲 int 的函數,Math.max() 靜態方法符合要求。

  2. 對象名::非靜態方法名 : 對象的方法引用

    例如定義一個 println 函數式接口對象,用於輸出字符串:

    Consumer<String> println = System.out::println;

    Consumer<String> 表示有一個 String 類型參數且無返回值的函數,System.out.println() 方法符合要求。

  3. 類名::new : 構造方法引用

    例如定義一個 createHashMap 函數式接口對象,用於建立一個 HashMap

    Supplier<HashMap> createHashMap = HashMap::new;

    Supplier<HashMap> 表示有一個無參數且返回值爲 HashMap 的函數,HashMap 的構造函數符合要求。

  4. 類名::非靜態方法名 : 文檔中解釋爲:Reference to an instance method of an arbitrary object of a particular type 。若是不理解的話,下面舉個例子來講明一下。

    定義一個 concat 函數式接口對象,用於拼接兩個字符串:

    BinaryOperator<String> concat = String::concat;

    BinaryOperator<String> 表示有兩個 String 類型參數且返回值爲 String 的函數。注意 String 類的 concat 不是靜態方法,且 String.concat(String str) 只有一個參數,看似不符合要求。實際上它至關於:

    BinaryOperator<String> concat = (s1, s2) -> s1.concat(s2);

    即調用第一個參數 s1concat 方法,傳入參數 s2

擴展閱讀

Java 8 Stream 總結

參考文檔

關注我

掃碼關注

相關文章
相關標籤/搜索