亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

函數式接口

在上個小節的最后,我們提到了函數式接口的概念,也知道了想要使用Lambda表達式,則必須依賴函數式接口。本小節我們將學習函數式接口相關的知識,包括什么是函數式接口為什么需要函數式接口,如何自定義一個函數式接口,如何創建函數式接口的對象,以及一些 Java 內置的函數式接口的詳細介紹等。本小節內容較為簡單,但需要讀者有Lambda表達式前置知識,學習重點是要了解 Java 內置函數式接口。

1. 什么是函數式接口

函數是接口(Functional Interface)的定義非常容易理解:只有一個抽象方法的接口,就是函數式接口??梢酝ㄟ^Lambda表達式來創建函數式接口的對象。

我們來看一個在之前我們就經常使用的Runnable接口,Runnable接口就是一個函數式接口,下面的截圖為 Java 源碼:

我們看到Runnable接口中只包含一個抽象的run()方法,并且在接口上標注了一個@FuncationInterface注解,此注解就是 Java 8 新增的注解,用來標識一個函數式接口。

2. 為什么需要函數式接口

學習了這么久的 Java,我們對 Java 是純種的面向對象的編程語言這一概念,可能有了一定的感觸,在 Java 中,一切皆是對象。但是隨著Python、scala等語言的興起,函數式編程的概念得到開發者們的推崇,Java 不得不做出調整以支持更廣泛的技術要求。

在面向函數編程的語言中,Lambda表達式的類型就是函數,但是在 Java 中,Lambda表達式的類型是對象而不是函數,他們必須依賴于一種特別的對象類型——函數式接口。所以說,Java 中的Lambda表達式就是一個函數式接口的對象。我們之前使用匿名實現類表示的對象,都可以使用Lambda表達式來表示。

3. 自定義函數式接口

想要自定義一個函數式接口也非常簡單,在接口上做兩件事即可:

  1. 定義一個抽象方法:注意,接口中只能有一個抽象方法;
  2. 在接口上標記@FunctionalInterface注解:當然也可以不標記,但是如果錯寫了多個方法,編輯器就不能自動檢測你定義的函數式接口是否有問題了,所以建議還是寫上吧。
/**
 * 自定義函數式接口
 * @author colorful@TaleLin
 */
@FunctionalInterface
public interface FunctionalInterfaceDemo {

    void run();

}

由于標記了@FunctionalInterface注解,下面接口下包含兩個抽象方法的這種錯誤寫法,編譯器就會給出提示:

4.創建函數式接口對象

在上面,我們自定義了一個函數式接口,那么如何創建它的對象實例呢?

我們可以使用匿名內部類來創建該接口的對象,實例代碼如下:

/**
 * 測試創建函數式接口對象
 * @author colorful@TaleLin
 */
public class Test {

    public static void main(String[] args) {
        // 使用匿名內部類方式創建函數式接口
        FunctionalInterfaceDemo functionalInterfaceDemo = new FunctionalInterfaceDemo() {
            @Override
            public void run() {
                System.out.println("匿名內部類方式創建函數式接口");
            }
        };
        functionalInterfaceDemo.run();
    }

}

運行結果:

匿名內部類方式創建函數式接口

現在,我們學習了Lambda表達式,也可以使用Lambda表達式來創建,這種方法相較匿名內部類更加簡潔,也更推薦這種做法。實例代碼如下:

/**
 * 測試創建函數式接口對象
 * @author colorful@TaleLin
 */
public class Test {

    public static void main(String[] args) {
        // 使用 Lambda 表達式方式創建函數式接口
        FunctionalInterfaceDemo functionalInterfaceDemo = () -> System.out.println("Lambda 表達式方式創建函數式接口");
        functionalInterfaceDemo.run();
    }

}

運行結果:

Lambda 表達式方式創建函數式接口

當然,還有一種更笨的方法,寫一個接口的實現類,通過實例化實現類來創建對象。由于比較簡單,而且不符合我們學習函數式接口的初衷,這里就不再做實例演示了。

5. 內置的函數式接口介紹

通過上面一系列介紹和演示,相信對于函數式接口的概念和使用,你已經爛熟于心了。但是只知道這些還不夠用,下面的內容才是本小節的重點,Java 中內置了豐富的函數式接口,位于java.util.function包下,學習這些函數式接口有助于我們理解 Java 函數式接口的真正用途和意義。

Java 內置了 4 個核心函數式接口:

  1. Comsumer<T>消費型接口: 表示接受單個輸入參數但不返回結果的操作,包含方法:void accept(T t),可以理解為消費者,只消費(接收單個參數)、不返回(返回為 void);
  2. Supplier<T>供給型接口:表示結果的供給者,包含方法T get(),可以理解為供給者,只提供(返回T類型對象)、不消費(不接受參數);
  3. Function<T, R>函數型接口:表示接受一個T類型參數并返回R類型結果的對象,包含方法R apply(T t)
  4. Predicate<T>斷言型接口:確定T類型的對象是否滿足約束,并返回boolean值,包含方法boolean test(T t)。

我們在 Java 的 api 文檔中可以看到有一些方法的形參,會出現上面幾類接口,我們在實例化這些接口的時候,就可以使用Lambda表達式的方式來實例化。

我們下面看幾個實例,消費型接口使用實例:

實例演示
預覽 復制
復制成功!
import java.util.function.Consumer;

/**
 * Java 內置4大核心h函數式接口 —— 消費型接口
 * Consumer<T> void accept(T t)
 * @author colorful@TaleLin
 */
public class FunctionalInterfaceDemo1 {

    public static void main(String[] args) {
        Consumer<String> consumer = s -> System.out.println(s);
        consumer.accept("只消費,不返回");
    }

}
運行案例 點擊 "運行案例" 可查看在線運行效果

運行結果:

只消費,不返回

供給型接口使用實例:

實例演示
預覽 復制
復制成功!
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * Java 內置4大核心h函數式接口 —— 供給型接口
 * Supplier<T> T get()
 * @author colorful@TaleLin
 */
public class FunctionalInterfaceDemo2 {

    public static void main(String[] args) {
        Supplier<String> supplier = () -> "只返回,不消費";
        String s = supplier.get();
        System.out.println(s);
    }

}
運行案例 點擊 "運行案例" 可查看在線運行效果

運行結果:

只返回,不消費

下面我們使用斷言型接口,來實現一個根據給定的規則,來過濾字符串列表的方法,實例如下:

實例演示
預覽 復制
復制成功!
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

/**
 * Java 內置4大核心函數式接口 —— 斷言型接口
 * Predicate<T> boolean test(T t)
 * @author colorful@TaleLin
 */
public class FunctionalInterfaceDemo3 {

    /**
     * 根據 Predicate 斷言的結果,過濾 list 中的字符串
     * @param list 待過濾字符串
     * @param predicate 提供規則的接口實例
     * @return 過濾后的列表
     */
    public static List<String> filterStringList(List<String> list, Predicate<String> predicate) {
        // 過濾后的字符串列表
        ArrayList<String> arrayList = new ArrayList<>();
        for (String string: list) {
            if (predicate.test(string)) {
                // 如果 test 是 true,則將元素加入到過濾后的列表中
                arrayList.add(string);
            }
        }
        return arrayList;
    }

    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("Java");
        arrayList.add("PHP");
        arrayList.add("Python");
        arrayList.add("JavaScript");
        System.out.println("過濾前:");
        System.out.println(arrayList);

        List<String> filterResult = filterStringList(arrayList, new Predicate<String>() {
            @Override
            public boolean test(String s) {
                // 返回字符串中是否包含 P
                return s.contains("P");
            }
        });
        System.out.println("過濾后:");
        System.out.println(filterResult);
    }

}
運行案例 點擊 "運行案例" 可查看在線運行效果

運行結果:

過濾前:
[Java, PHP, Python, JavaScript]
過濾后:
[PHP, Python]

當然,我們學習了Lambda表達式,在main()方法中就可以不再使用匿名內部類了,改寫main()方法中調用filterStringList()方法的代碼:

List<String> filterResult = filterStringList(arrayList, s -> s.contains("P"));

上面的實例代碼可能有些難以理解,跟著我的節奏來解讀一下:

  • 先定義一個方法List<String> filterStringList(List<String> list, Predicate<String> predicate),此方法用于根據指定的規則過濾字符串列表,接收的第一個參數為待過濾列表,第二個參數是一個函數式接口類型的規則,注意,這個參數就是規則的制定者;
  • 再看filterStringList()方法的方法體,方法體內部對待過濾列表進行了遍歷,會調用Predicate<T>接口下的boolean test(T t)方法,判斷每一個字符串是否符合規則,符合規則就追加到新的列表中,最終返回一個新的過濾后的列表;
  • main()方法中,我們調用了上面定義的filterStringList()方法,第一個參數就是待過濾列表,這里的第二個參數,是我們創建的一個斷言型接口的對象,其重寫的test(String s)方法就是過濾規則關鍵所在,方法體就是判斷s字符串是否包含P字符,并一個 boolean 類型的結果;
  • 理解了第二個參數通過匿名內部類創建對象的方式,再改寫成通過Lambda表達式的方式創建對象,就不難理解了。

上面我們介紹了核心的內置函數式接口,理解了這些接口的使用,其他接口就不難理解了??煞?a >官方文檔來查看更多。

6. 小結

通過本小節的學習,我們知道了函數式接口就是只有一個抽象方法的接口,要使用Lambda表達式,就必須依賴函數式接口;自定義函數接口建議使用@FunctionalInterface注解來進行標注,當然如果通過 Java 內置的函數式接口就可以滿足我們的需求,就不需要我們自己自定義函數式接口了。本小節的最后,我們通過一個較為復雜的函數式接口實例,實現了一個過濾字符串列表的方法,如果還是不能完全理解,建議同學下面多加練習。