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

函數式接口概述

在 Java 里面,所有的方法參數都是有固定類型的,比如將數字 9 作為參數傳遞給一個方法,它的類型是 int;字符串 “9” 作為參數傳遞給方法,它的類型是 String。那么 Lambda 表達式的類型由是什么呢?通過本節我們學習什么是函數式接口,它與 Lambda 表達式的關系。

1. 什么是函數式接口

函數式接口(Functional Interface)就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口,它可以被隱式轉換為 Lambda 表達式。

Tips: 換句話說函數式接口就是 Lambda 表達式的類型。

在函數式接口中,單一方法的命名并不重要,只要方法簽名和 Lambda 表達式的類型匹配即可。

Tips: 通常我們會為接口中的參數其一個有意義的名字來增加代易讀性,便于理解參數的用途。

函數式接口有下面幾個特點:

  1. 接口有且僅有一個抽象方法;
  2. 允許定義靜態方法;
  3. 允許定義默認方法;
  4. 允許 java.lang.Object 中的 public 方法;
  5. 推薦使用 @FunctionInterface 注解(如果一個接口符合函數式接口的定義,加不加該注解都沒有影響,但加上該注解可以更好地讓編譯器進行檢查)。

我們來看函數式接口的例子:

實例演示
預覽 復制
復制成功!
@FunctionalInterface
interface TestFunctionalInterface
{
    //抽象方法
    public void doTest();
    //java.lang.Object中的public方法
    public boolean equals(Object obj);
    public String toString();
    //默認方法
    public default void doDefaultMethod(){System.out.println("call dodefaultMethod");}
    //靜態方法
    public static void doStaticMethod(){System.out.println("call doStaticMethod");}


    public static void main(String...s){
        //實現抽象方法
        TestFunctionalInterface test = ()->{
            System.out.println("call doTest");
        };
        //調用抽象方法
        test.doTest();
        //調用默認方法
        test.doDefaultMethod();
        //調用靜態方法
        TestFunctionalInterface.doStaticMethod();
        //調用toString方法
        System.out.println(test.toString());
    }
}
運行案例 點擊 "運行案例" 可查看在線運行效果

我們將得到如下結果:

call doTest
call dodefaultMethod
call doStaticMethod
com.github.x19990416.item.TestFunctionalInterface$$Lambda$1/0x00000008011e0840@63961c42

我們通過 toString 方法可以發現,test 對象被便已成為 TestFunctionalInterface 的一個 Lambda 表達式。

2. @FunctionalInterface

接下來我們再來看下 @FunctionalInterface注解的作用:

首先我們定義一個接口 TestFunctionalInterface,包含兩個方法 doTest1doTest2

interfact TestFunctionalInterface{
    //一個抽象方法
    public void doTest1();
    //另一個抽象方法
    public void doTest2();
}

此時對于編譯期而言我們的代碼是沒有任何問題的,它會認為這就是一個普通的接口。當我們使用 @FunctionalInterface 后:

//這是一個錯誤的例子?。。?!
@FunctionalInterface
interfact TestFunctionalInterface{
    //一個抽象方法
    public void doTest1();
    //另一個抽象方法
    public void doTest2();
}

此時,會告訴編譯器這是一個函數式接口,但由于接口中有兩個抽象方法,不符合函數式接口的定義,此時編譯器會報錯:

Multiple non-overriding abstract methods found in interface

3. 常用的函數式接口

JDK 8 之后新增了一個函數接口包 java.util.function 這里面包含了我們常用的一些函數接口:

接口 參數 返回類型 說明
Predicate T boolean 接受一個輸入參數 T,返回一個布爾值結果
Supplier None T 無參數,返回一個結果,結果類型為 T
Consumer T void 代表了接受一個輸入參數 T 并且無返回的操作
Function<T,R> T R 接受一個輸入參數 T,返回一個結果 R
UnaryOperator T T 接受一個參數為類型 T,返回值類型也為 T
BinaryOperator (T,T) T 代表了一個作用于于兩個同類型操作符的操作,并且返回了操作符同類型的結果

3.1 Predicate

條件判斷并返回一個Boolean值,包含一個抽象方法 (test) 和常見的三種邏輯關系 與 (and) 、或 (or) 、非 (negate) 的默認方法。

Predicate 接口通過不同的邏輯組合能夠滿足我們常用的邏輯判斷的使用場景。

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

public class DemoPredicate {
    public static void main(String[] args) {
        //條件判斷
        doTest(s -> s.length() > 5);
        //邏輯非
        doNegate(s -> s.length() > 5);
        //邏輯與
        boolean isValid = doAnd(s -> s.contains("H"),s-> s.contains("w"));
        System.out.println("邏輯與的結果:"+isValid);
        //邏輯或
        isValid = doOr(s -> s.contains("H"),s-> s.contains("w"));
        System.out.println("邏輯或的結果:"+isValid);
    }

    private static void doTest(Predicate<String> predicate) {
        boolean veryLong = predicate.test("Hello World");
        System.out.println("字符串長度很長嗎:" + veryLong);
    }

    private static boolean doAnd(Predicate<String> resource, Predicate<String> target) {
        boolean isValid = resource.and(target).test("Hello world");
        return isValid;
    }

    private static boolean doOr(Predicate<String> one, Predicate<String> two) {
        boolean isValid = one.or(two).test("Hello world");
        return isValid;
    }
    private static void doNegate(Predicate<String> predicate) {
        boolean veryLong = predicate.negate().test("Hello World");
        System.out.println("字符串長度很長嗎:" + veryLong);
    }
}
運行案例 點擊 "運行案例" 可查看在線運行效果

結果如下:

字符串長度很長嗎:true
字符串長度很長嗎:false
邏輯與的結果:true
邏輯或的結果:true

3.2 Supplier

用來獲取一個泛型參數指定類型的對象數據(生產一個數據),我們可以把它理解為一個工廠類,用來創建對象。

Supplier 接口包含一個抽象方法 get,通常我們它來做對象轉換。

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

public class DemoSupplier {
    public static void main(String[] args) {
        String sA = "Hello ";
        String sB = "World ";
        System.out.println(
                getString(
                        () -> sA + sB
                )
        );
    }

    private static String getString(Supplier<String> stringSupplier) {
        return stringSupplier.get();
    }
}
運行案例 點擊 "運行案例" 可查看在線運行效果

結果如下:

Hello World 

上述例子中,我們把兩個 String 對象合并成一個 String。

3.3 Consumer

與 Supplier 接口相反,Consumer 接口用于消費一個數據。

Consumer 接口包含一個抽象方法 accept 以及默認方法 andThen 這樣 Consumer 接口可以通過 andThen 來進行組合滿足我們不同的數據消費需求。最常用的 Consumer 接口就是我們的 for 循環,for 循環里面的代碼內容就是一個 Consumer 對象的內容。

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

public class DemoConsumer {
    public static void main(String[] args) {
        //調用默認方法
        consumerString(s -> System.out.println(s));
        //consumer接口的組合
        consumerString(
                // toUpperCase()方法,將字符串轉換為大寫
                s -> System.out.println(s.toUpperCase()),
                // toLowerCase()方法,將字符串轉換為小寫
                s -> System.out.println(s.toLowerCase())
        );
    }

    private static void consumerString(Consumer<String> consumer) {
        consumer.accept("Hello");
    }

    private static void consumerString(Consumer<String> first, Consumer<String> second) {
        first.andThen(second).accept("Hello");
    }
}
運行案例 點擊 "運行案例" 可查看在線運行效果

結果如下:

Hello
HELLO
hello

在調用第二個 consumerString 的時候我們通過 andThen 把兩個 Consumer 組合起來,首先把 Hello 全部轉變成大寫,然后再全部轉變成小寫。

3.4 Function

根據一個類型的數據得到另一個類型的數據,換言之,根據輸入得到輸出。

Function 接口有一個抽象方法 apply 和默認方法 andThen,通過 andThen 可以把多個 Function 接口進行組合,是適用范圍最廣的函數接口。

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

public class DemoFunction {
    public static void main(String[] args) {
        doApply(s -> Integer.parseInt(s));
        doCombine(
                str -> Integer.parseInt(str)+10,
                i -> i *= 10
        );
    }

    private static void doApply(Function<String, Integer> function) {
        int num = function.apply("10");
        System.out.println(num + 20);
    }
    private static void doCombine(Function<String, Integer> first, Function<Integer, Integer> second) {
        int num = first.andThen(second).apply("10");
        System.out.println(num + 20);
    }
}
運行案例 點擊 "運行案例" 可查看在線運行效果

結果如下:

30
220

上述四個函數接口是最基本最常用的函數接口,需要熟悉其相應的使用場景并能夠熟練使用。 UnaryOperatorBinaryOperator 作用與 Funciton 類似,大家可以通過 Java 的源代碼進一步了解其作用。

4. 小結

本節,我們主要闡述了函數式接口的定義以及其與 Lambda 表達式的關系。并對新增的 java.util.function 包中常用的函數式接口進行了解釋。這些接口常用于集合處理中(我們將在后續的內容進一步學習),關于函數式接口主要記住一點,那就是:

接口有且僅有一個抽象方法