Java8: Functional Interface and Lambda Expression

寫在前面: 最近在看<<Java8實戰>>,感受這本書的排版,紙質,內容真的都超級棒,我的以爲這本書仍是很值得一讀.本文簡單或詳細介紹一下Java8的FunctionalInterface和Lambda表達式.java

What is Functional Interface?

函數式接口(Functional Interface)是在Java8纔開始引入的.首先看一下Java普通接口和函數式接口的區別:express

  • Java普通接口: 指的是一些具備相同屬性和行爲的類的抽象.
  • 函數式接口: 也是一樣的理解方式,它是對一些類似的方法的抽象.

How can we say those methods are similar methods?

1.若是定義的是一個泛型的函數式接口的話,好比:ide

@FunctionalInterface
public interface MyFunction1<T, R> {
    R calculate(T t);
}

那麼全部只有一個參數並有返回值的函數都是MyFunction1的一個實例.也就是說這些方法都是類似的方法.函數

2.若是定義的是一個具體的函數式接口,好比:code

@FunctionalInterface
public interface MyFunction2 {
    void print(Integer i);
}

那麼全部參數是Integer類型的而且沒有返回值的函數都是MyFunction2的一個實例.也就是說這些方法都是類似的方法.對象

Java的接口能夠當作函數的參數,那麼函數式接口天然也能夠,這就叫作行爲參數化.接口

How to define and use a funtional interface?

在Java8中已經定義了好多的函數式接口,好比:get

  • java.lang.Runnable
  • java.util.Comparator
  • java.util.function.Predicate
  • java.util.function.Consumer

那麼定義一個函數式接口,有哪些須要注意的地方呢?it

  • 1.添加@FunctionalInterface註解.
  • 2.有且只有一個抽象方法.能夠有多個default方法和static方法,可是這些方法都必須實現.

對於@FunctionalInterface註解,其實不加也能夠,徹底是能夠經過編譯的,但前提是這個接口須要知足上面的第二個注意點.io

但又是爲何須要@FunctionalInterface?首先咱們先看一個例子:

假設咱們定義了一個函數式接口,可是忘記了添加了@FunctionInterface,同時這個函數式接口還沒被其餘地方引用,若是這個時候咱們又添加了一個抽象方法,是不會報錯的.由於這個接口有了多個抽象方法,因此這個接口就是一個普通的接口,而不是咱們所指望的函數式接口.

這時候,若是添加了@FunctionalInterface註解的話,就會報錯,提示說這個接口具備多個抽象方法.實際上,我的理解@FunctionalInterface就是手動添加約束,說明這個接口就是函數式接口,只能有一個抽象方法.

接下來編寫並使用一個自定義函數式接口

// define a functional interface
@FunctionalInterface
public interface MyFunction<T, R> {
    R convert(T t);
}

// use a function interface
public class Test {
    public static void main(String[] args) {
        MyFunction<Long, String> f = (Long l) -> String.valueOf(l);
        System.out.println(f.convert(10L));  // f: convert a Long to String 
        MyFunction<String, Long> g = (String s) -> Long.parseLong(s);
        System.out.println(g.convert("10")); // g: convert a String to Long
    }
}

What is Lambda expression?

我們能夠看一看<<Java8實戰>>這本書中的定義:

能夠把Lambda表達式理解爲簡潔地表示可傳遞的匿名函數的一種方式: 它沒有名稱,但它有參數列表,函數主體,返回類型,可能還有一個能夠拋出的異常列表.

  • 匿名: 是由於它不像普通的方法有一個明確的名稱
  • 函數: 是由於Lambda函數不像方法那樣屬於某個特定的類,但又和方法同樣,有參數列表,函數主體,返回值類型,還可能有能夠拋出的異常列表
  • 傳遞: Lambda表達式能夠做爲參數傳遞給方法或存儲在變量中
  • 簡潔: 無須像匿名類那樣寫不少模板代碼

我的理解,Lambda表達式徹底是服務於函數式接口的,就是爲了在建立一個函數式接口實例時更加的直觀,簡潔.好比我們要建立一個匿名類對象時,能夠看一下普通接口和函數式接口的區別:

// a common interface
public interface Common {
    void f();
    void g();
}

@FunctionalInterface
public interface MyFunction<T, R> {
    R convert(T);
}

public class Test {
    public static void main(String[] args){
        // create a instance of common interface
        Common c = new Common() {
            @Override
            public void f() {}
            @Override
            public void g() {}
        };
        // create a instance of functional interface
        MyFunction<Long, String> f = (Long l) -> String.valueOf(l);
    }
}

Method Reference

提及方法引用以前,我們先看一個例子

// 1
    MyFunction<Long, String> f = (Long l) -> String.valueOf(l);
    // 2: 經過Lambda的上下文推斷出l的數據類型
    MyFunction<Long, String> f = l -> String.valueOf(l);  // 
    // 3
    MyFunction<Long, String> f = String::valueOf;

第一種,第二種和第三種寫法的做用徹底是同樣的,但顯然第三種的寫法更加簡潔.形如xxx::xxx這樣的表達式就是方法引用.方法引用能夠被看作僅僅調用特定方法的Lambda的一種快捷寫法.

How many ways to replace lambda expression with method reference?

總共有四種方法引用,下面我們根據一個實例來講明並解釋這幾種方法引用.

// common class
public class FunctionInstance {
    public Integer getLength(String str) {
        return Optional.ofNullable(str).orElse("").length();  // Optional也是Java8引入的
    }
}
// test class
public class MethodReference {
    public static void main(String[] args) {
        // 1.指向靜態方法的方法引用
        //Function<String, Integer> f1 = s -> Integer.parseInt(s);
        Function<String, Integer> f1 = Integer::parseInt;

        // 2.指向任意類型實例方法的方法引用,s爲內部對象
        //Predicate<String> f2 = s -> s.isEmpty();
        Predicate<String> f2 = String::isEmpty;

        // 3.指向現有對象(外部對象)的實例方法的方法引用, instance爲外部對象
        //Function<String, Integer> f3 = s -> instance.getLength(s);
        FunctionInstance instance = new FunctionInstance();
        Function<String, Integer> f3 = instance::getLength;

        // 4.構造函數引用
        //Supplier<MethodReference> f4 = () -> new MethodReference();
        Supplier<MethodReference> f4 = MethodReference::new;
    }
}

2和3有人可能會混淆, 反正一開始看的時候我是沒區分出來.其實就看參數是爲內部對象仍是外部對象.內部對象就是用2,外部對象就是用3

  • 內部對象: 指Lambda表達式中的對象,好比上面的s, 傳入的參數,屬於內部對象
  • 外部對象: 指的是Lambda表達式外的對象,好比上面的instance

Summary

函數式接口: 就是一個特殊的接口,只能有一個抽象方法.因此能夠將函數式接口想象成是一個函數類型(好比Predicate,Consumer或自定義的函數式接口).

Lambda表達式: 則是簡化了匿名類對象的建立.正如我在上文所說的,Lambda表達式徹底是服務於函數式接口的,正是由於了函數式接口的特殊性,因此才使用了Lambda表達式來建立一個函數類型實例,而不是像匿名類那樣寫一堆無用的模板代碼.

相關文章
相關標籤/搜索