Java 8 函數式接口和Lambda學習筆記(二)

上篇博客Java 8 Lambda學習筆記(一)主要記錄了Lambda表達式的相關知識點,這篇博客將重點介紹函數式接口

前面也說了,函數式接口其實本質上還是一個接口,但是它是一種特殊的接口:SAM類型的接口(Single Abstract Method)。

函數式接口有三個特性:

1. 該接口只有一個抽象方法,多了不行,少了也不行。

2. 可以有靜態方法和默認方法,因爲這兩種方法都是已經實現的了。

3. 抽象方法一定不能跟Object類中的方法同名。

@FunctionalInterface
interface Fun{
	//只設置一個抽象方法 ------可以正常運行
	abstract int spend(int remaining,int money);
	
	//加入靜態方法和默認方法 ----可以正常運行
	static void staticMethod() {
		System.out.println("There is a static method!");
	}
	default void defaultMethod() {
		System.out.println("There is a default method!");
	}
	
	//設置Object中可以重寫的抽象方法 ----編譯報錯
//	abstract boolean equals(Object obj);
	
}

Java 8 新特性提供了函數式接口,是爲了更好的支持函數式編程。Java也提供了註解@FunctionalInterface,標註了該註解的接口,編譯器就會自動幫我們檢查該接口是否符合函數式接口的格式。

配合Lambda表達式的具體使用

函數式接口就是用來配套Lambda表示使用的。也就是說,一個自定義的函數接口,與之對應的Lambda表達式的參數就是這個默認抽象方法的參數,返回值就是這個默認抽象方法的返回值。 

package com.wjb.lambda;
/**
 * 函數式接口:
 * 		1.只能有一個抽象方法
		2.可以有靜態方法和默認方法,因爲這兩種方法都是已經實現的了
		3.這個抽象方法一定不能跟Object類中的方法同名。
 * @author Administrator
 *
 */
@FunctionalInterface
interface Fun{
	//只設置一個抽象方法 ------可以正常運行
	abstract int spend(int remaining,int money);
	
	//加入靜態方法和默認方法 ----可以正常運行
	static void staticMethod() {
		System.out.println("There is a static method!");
	}
	default void defaultMethod() {
		System.out.println("There is a default method!");
	}
	
	//設置Object中可以重寫的抽象方法 ----編譯報錯
//	abstract boolean equals(Object obj);
	
}
public class FunInterface{
	public static void main(String[] args) {
		Fun fun = (a,b) -> (a-b); //參數就是spend()方法的參數,返回值a-b就是spend()方法的返回值
		System.out.println(fun.spend(100, 5));
	}
}

 五個常用的函數接口

java提供的函數接口在java.util.function包中,其中比較重要的是以下5個接口。我認爲jdk8給我們提供的這些接口是提供了一些模式(幫助我們抽象出來了一些行爲),在使用的時候根據官方抽象的接口進行實現也更加方便。 

1.Consumer (消費模式)

功能: 顧名思義,就是消費掉傳入的一個泛型對象,不返回任何值。那這個接口有什麼實際使用場景呢?其實如果查看foreach方法的底層代碼的話,這個方法所需要的參數就必須是一個Consumer接口類型的參數。

查看Consumer接口的源碼可以知道,其唯一的抽象方法是

void accept(T t);

下面寫一個實例來跑一下:

/**	
     * consumer接口測試	
     */	
    @Test	
    public void test_Consumer() {	
        //1. 使用consumer接口實現方法	
        Consumer<String> consumer = new Consumer<String>() {	
	
            @Override	
            public void accept(String s) {	
                System.out.println(s);	
            }	
        };	
        Stream<String> stream = Stream.of("aaa", "bbb", "ddd", "ccc", "fff");	
        stream.forEach(consumer);	
	
        System.out.println("********************");	
	
        //2.使用lambda表達式,forEach方法需要的就是一個Consumer接口	
        stream = Stream.of("aaa", "bbb", "ddd", "ccc", "fff");	
        Consumer<String> consumer1 = (s) -> System.out.println(s);//lambda表達式返回的就是一個Consumer接口	
        stream.forEach(consumer1);	
        //更直接的方式	
        //stream.forEach((s) -> System.out.println(s));	
        System.out.println("********************");	
	
        //3.使用方法引用,方法引用也是一個consumer	
        stream = Stream.of("aaa", "bbb", "ddd", "ccc", "fff");	
        Consumer consumer2 = System.out::println;	
        stream.forEach(consumer);	
        //更直接的方式	
        //stream.forEach(System.out::println);	
    }

分析代碼可知:

在代碼1中,我們直接創建 Consumer 接口,並且實現了一個名爲 accept 的方法,當我們發現 forEach 需要一個 Consumer類型的參數的時候,傳入之後,就可以輸出對應的值了。代碼2中,我們使用lambda表達式作爲Consumer。仔細的看一下你會發現,lambda 表達式返回值就是一個 Consumer;所以,你也就能夠理解爲什麼 forEach 方法可以使用 lamdda 表達式作爲參數了吧。

代碼3中,我們用了一個方法引用的方式作爲一個 Consumer ,同時也可以傳給 forEach 方法。

當然,除了上面使用的 Consumer 接口,還可以使用下面這些 Consumer 接口。IntConsumerDoubleConsumerLongConsumerBiConsumer,使用方法和上面一樣。

看完上面的實例我們可以總結爲兩點。

① Consumer是一個接口,並且只要實現一個 accept 方法,就可以作爲一個“消費者”輸出信息。
forEach 方法的參數必須是Consumer 類型,所以只要lambda 表達式、方法引用的返回值符合Consumer類型 ,他們就能夠作爲 forEach 方法的參數,並且輸出一個值。

 

2.Supplier (生產模式)

功能:與Consumer接口相反,Supplier接口是不傳遞參數,只返回一個值。我們來看一下源碼

T get();

可以理解爲,不接受任何參數,返回一個值,究竟返回什麼值由調用者決定----Lambda表達式的製造者。其實,說白了就是一個容器,可以用來存儲數據,然後可以供其他方法使用的這麼一個接口。

下面具體實現一下:

**	
     * Supplier接口測試,supplier相當一個容器或者變量,可以存儲值	
     */	
    @Test	
    public void test_Supplier() {	
        //① 使用Supplier接口實現方法,只有一個get方法,無參數,返回一個值	
        Supplier<Integer> supplier = new Supplier<Integer>() {	
            @Override	
            public Integer get() {	
                //返回一個隨機值	
                return new Random().nextInt();	
            }	
        };	
	
        System.out.println(supplier.get());	
	
        System.out.println("********************");	
	
        //② 使用lambda表達式,	
        supplier = () -> new Random().nextInt();	
        System.out.println(supplier.get());	
        System.out.println("********************");	
	
        //③ 使用方法引用	
        Supplier<Double> supplier2 = Math::random;	
        System.out.println(supplier2.get());	
    }

我們通過創建一個 Supplier 對象,實現了一個 get 方法,這個方法無參數,返回一個值;所以,每次使用這個接口的時候都會返回一個值,並且保存在這個接口中,所以說是一個容器

 下面我們再來看看Supplier具體的使用場景:

/**	
     * Supplier接口測試2,使用需要Supplier的接口方法	
     */	
    @Test	
    public void test_Supplier2() {	
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);	
        //返回一個optional對象	
        Optional<Integer> first = stream.filter(i -> i > 4)	
                .findFirst();	
	
        //optional對象有需要Supplier接口的方法	
        //orElse,如果first中存在數,就返回這個數,如果不存在,就放回傳入的數	
        System.out.println(first.orElse(1));	
        System.out.println(first.orElse(7));	
	
        System.out.println("********************");	
	
        Supplier<Integer> supplier = new Supplier<Integer>() {	
            @Override	
            public Integer get() {	
                //返回一個隨機值	
                return new Random().nextInt();	
            }	
        };	
	
        //orElseGet,如果first中存在數,就返回這個數,如果不存在,就返回supplier返回的值	
        System.out.println(first.orElseGet(supplier));	
    }	

使用方法獲取到一個 Optional 對象,然後,在 Optional 對象中有  orElse 方法 和 orElseGet 是需要一個 Supplier 接口的。 

  • orElse:如果first中存在數,就返回這個數,如果不存在,就放回傳入的數

  • orElseGet:如果first中存在數,就返回這個數,如果不存在,就返回supplier返回的值

除了上面使用的 Supplier 接口,還可以使用下面這些 Supplier 接口。
IntSupplier 、DoubleSupplier 、LongSupplier 、BooleanSupplier,使用方法和上面一樣。

Supplier 總結

① Supplier 接口可以理解爲一個容器,用於裝數據的。
② Supplier 接口有一個 get 方法,可以返回值。

 

3.Predicate (判斷模式--謂詞

功能:傳遞一個參數,返回一個Boolean值。其實,這個就是一個類似於 bool 類型的判斷的接口。

 

還是來看一個實例

/**	
     * Predicate謂詞測試,謂詞其實就是一個判斷的作用類似bool的作用	
     */	
    @Test	
    public void test_Predicate() {	
        //① 使用Predicate接口實現方法,只有一個test方法,傳入一個參數,返回一個bool值	
        Predicate<Integer> predicate = new Predicate<Integer>() {	
            @Override	
            public boolean test(Integer integer) {	
                if(integer > 5){	
                    return true;	
                }	
                return false;	
            }	
        };	
	
        System.out.println(predicate.test(6));	
	
        System.out.println("********************");	
	
        //② 使用lambda表達式,	
        predicate = (t) -> t > 5;	
        System.out.println(predicate.test(1));	
        System.out.println("********************");	
	
    }

這段代碼中,創建了一個 Predicate 接口對象,其中,實現類 test 方法,需要傳入一個參數,並且返回一個 bool 值,所以這個接口作用就是判斷!然後,調用 test 方法,傳入一個值,就會返回一個 bool 值。也可以使用lambda 表達式返回一個 Predicate 接口,然後調用 test 方法!

好,我們再看一個實例,看看Predicate具體的使用場景:

/**	
     * Predicate謂詞測試,Predicate作爲接口使用	
     */	
    @Test	
    public void test_Predicate2() {	
        //① 將Predicate作爲filter接口,Predicate起到一個判斷的作用	
        Predicate<Integer> predicate = new Predicate<Integer>() {	
            @Override	
            public boolean test(Integer integer) {	
                if(integer > 5){	
                    return true;	
                }	
                return false;	
            }	
        };	
	
        Stream<Integer> stream = Stream.of(1, 23, 3, 4, 5, 56, 6, 6);	
        List<Integer> list = stream.filter(predicate).collect(Collectors.toList());	
        list.forEach(System.out::println);	
	
        System.out.println("********************");	
	
    }

這段代碼,首先創建一個 Predicate 對象,然後實現 test 方法,在 test 方法中做一個判斷:如果傳入的參數大於 5 ,就返回 true,否則返回 false;然後調用 Stream 的 filter 方法,filter 方法需要的參數就是 Predicate 接口,所以在這裏只要大於 5 的數據就會輸出。

總結一下兩點:

① Predicate 是一個謂詞型接口,其實只是起到一個判斷作用。
② Predicate 通過實現一個 test 方法做判斷。

 

4. Function (功能模式)

功能:傳遞1個參數,返回1個參數。作用就是轉換作用,將輸入數據轉換成另一種形式的輸出數據。

第一個實例:

/**	
     * Function測試,function的作用是轉換,將一個值轉爲另外一個值	
     */	
    @Test	
    public void test_Function() {	
        //① 使用map方法,泛型的第一個參數是轉換前的類型,第二個是轉化後的類型	
        Function<String, Integer> function = new Function<String, Integer>() {	
            @Override	
            public Integer apply(String s) {	
                return s.length();//獲取每個字符串的長度,並且返回	
            }	
        };	
	
        Stream<String> stream = Stream.of("aaa", "bbbbb", "ccccccv");	
        Stream<Integer> stream1 = stream.map(function);	
        stream1.forEach(System.out::println);	
	
        System.out.println("********************");	
	
    }

輸出結果是:3 5 7

上面代碼創建了一個 Function 接口對象,實現了一個 apply 方法,這個方法有一個輸入參數和一個輸出參數。其中,泛型的第一個參數是轉換前的類型,第二個是轉化後的類型。

在上面的代碼中,就是獲取字符串的長度,然後將每個字符串的長度作爲返回值返回。

在 Function 接口的重要應用不得不說 Stream 類的 map 方法了,map 方法傳入一個 Function 接口,返回一個轉換後的 Stream類。

除了上面使用的 Function 接口,還可以使用下面這些 Function 接口。
IntFunction 、DoubleFunction 、LongFunction 、ToIntFunction 、ToDoubleFunction 、DoubleToIntFunction 等等,使用方法和上面一樣。

 總結一下:

① Function 接口是一個功能型接口,是一個轉換數據的作用。
② Function 接口實現 apply 方法來做轉換。

 

5.BiXXXX (BiFunction爲例)

功能: 傳遞兩個個參數,得到一個返回值。 

唯一的抽象方法是apply。

 R apply(T t, U u);

下面看一個實例:

public class FunctionTest {

    public static void main(String[] args) {


        BiFunction<Integer,Integer,Integer>  biFunction= (i1,i2) -> i1+i2;

        System.out.println(biFunction.apply(1,2));
    }
}

 BiFuntion中前兩個Integer是輸入參數類型,最後一個Integer是結果參數類型。

 

 

參考文獻

Lambda表達式和函數式接口

面試又掛了,你理解了 Java 8 的 Consumer、Supplier、Predicate和Function嗎?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章