函數式接口概述
在 Java 里面,所有的方法參數都是有固定類型的,比如將數字 9 作為參數傳遞給一個方法,它的類型是 int;字符串 “9” 作為參數傳遞給方法,它的類型是 String。那么 Lambda 表達式的類型由是什么呢?通過本節我們學習什么是函數式接口,它與 Lambda 表達式的關系。
1. 什么是函數式接口
函數式接口(Functional Interface)就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口,它可以被隱式轉換為 Lambda 表達式。
Tips: 換句話說函數式接口就是 Lambda 表達式的類型。
在函數式接口中,單一方法的命名并不重要,只要方法簽名和 Lambda 表達式的類型匹配即可。
Tips: 通常我們會為接口中的參數其一個有意義的名字來增加代易讀性,便于理解參數的用途。
函數式接口有下面幾個特點:
- 接口有且僅有一個抽象方法;
- 允許定義靜態方法;
- 允許定義默認方法;
- 允許
java.lang.Object
中的public
方法; - 推薦使用
@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
,包含兩個方法 doTest1
和 doTest2
:
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
上述四個函數接口是最基本最常用的函數接口,需要熟悉其相應的使用場景并能夠熟練使用。 UnaryOperator
和 BinaryOperator
作用與 Funciton
類似,大家可以通過 Java
的源代碼進一步了解其作用。
4. 小結

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