上篇博客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 接口。IntConsumer、DoubleConsumer、LongConsumer、BiConsumer
,使用方法和上面一樣。
看完上面的實例我們可以總結爲兩點。
① 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是結果參數類型。
參考文獻