函數(shù)式接口概述
在 Java 里面,所有的方法參數(shù)都是有固定類型的,比如將數(shù)字 9 作為參數(shù)傳遞給一個(gè)方法,它的類型是 int;字符串 “9” 作為參數(shù)傳遞給方法,它的類型是 String。那么 Lambda 表達(dá)式的類型由是什么呢?通過(guò)本節(jié)我們學(xué)習(xí)什么是函數(shù)式接口,它與 Lambda 表達(dá)式的關(guān)系。
1. 什么是函數(shù)式接口
函數(shù)式接口(Functional Interface)就是一個(gè)有且僅有一個(gè)抽象方法,但是可以有多個(gè)非抽象方法的接口,它可以被隱式轉(zhuǎn)換為 Lambda 表達(dá)式。
Tips: 換句話說(shuō)函數(shù)式接口就是 Lambda 表達(dá)式的類型。
在函數(shù)式接口中,單一方法的命名并不重要,只要方法簽名和 Lambda 表達(dá)式的類型匹配即可。
Tips: 通常我們會(huì)為接口中的參數(shù)其一個(gè)有意義的名字來(lái)增加代易讀性,便于理解參數(shù)的用途。
函數(shù)式接口有下面幾個(gè)特點(diǎn):
- 接口有且僅有一個(gè)抽象方法;
- 允許定義靜態(tài)方法;
- 允許定義默認(rèn)方法;
- 允許
java.lang.Object
中的public
方法; - 推薦使用
@FunctionInterface
注解(如果一個(gè)接口符合函數(shù)式接口的定義,加不加該注解都沒(méi)有影響,但加上該注解可以更好地讓編譯器進(jìn)行檢查)。
我們來(lái)看函數(shù)式接口的例子:
@FunctionalInterface
interface TestFunctionalInterface
{
//抽象方法
public void doTest();
//java.lang.Object中的public方法
public boolean equals(Object obj);
public String toString();
//默認(rèn)方法
public default void doDefaultMethod(){System.out.println("call dodefaultMethod");}
//靜態(tài)方法
public static void doStaticMethod(){System.out.println("call doStaticMethod");}
public static void main(String...s){
//實(shí)現(xiàn)抽象方法
TestFunctionalInterface test = ()->{
System.out.println("call doTest");
};
//調(diào)用抽象方法
test.doTest();
//調(diào)用默認(rèn)方法
test.doDefaultMethod();
//調(diào)用靜態(tài)方法
TestFunctionalInterface.doStaticMethod();
//調(diào)用toString方法
System.out.println(test.toString());
}
}
我們將得到如下結(jié)果:
call doTest
call dodefaultMethod
call doStaticMethod
com.github.x19990416.item.TestFunctionalInterface$$Lambda$1/0x00000008011e0840@63961c42
我們通過(guò) toString
方法可以發(fā)現(xiàn),test
對(duì)象被便已成為 TestFunctionalInterface
的一個(gè) Lambda 表達(dá)式。
2. @FunctionalInterface
接下來(lái)我們?cè)賮?lái)看下 @FunctionalInterface
注解的作用:
首先我們定義一個(gè)接口 TestFunctionalInterface
,包含兩個(gè)方法 doTest1
和 doTest2
:
interfact TestFunctionalInterface{
//一個(gè)抽象方法
public void doTest1();
//另一個(gè)抽象方法
public void doTest2();
}
此時(shí)對(duì)于編譯期而言我們的代碼是沒(méi)有任何問(wèn)題的,它會(huì)認(rèn)為這就是一個(gè)普通的接口。當(dāng)我們使用 @FunctionalInterface
后:
//這是一個(gè)錯(cuò)誤的例子?。。?!
@FunctionalInterface
interfact TestFunctionalInterface{
//一個(gè)抽象方法
public void doTest1();
//另一個(gè)抽象方法
public void doTest2();
}
此時(shí),會(huì)告訴編譯器這是一個(gè)函數(shù)式接口,但由于接口中有兩個(gè)抽象方法,不符合函數(shù)式接口的定義,此時(shí)編譯器會(huì)報(bào)錯(cuò):
Multiple non-overriding abstract methods found in interface
3. 常用的函數(shù)式接口
JDK 8 之后新增了一個(gè)函數(shù)接口包 java.util.function
這里面包含了我們常用的一些函數(shù)接口:
接口 | 參數(shù) | 返回類型 | 說(shuō)明 |
---|---|---|---|
Predicate | T | boolean | 接受一個(gè)輸入?yún)?shù) T ,返回一個(gè)布爾值結(jié)果 |
Supplier | None | T | 無(wú)參數(shù),返回一個(gè)結(jié)果,結(jié)果類型為 T |
Consumer | T | void | 代表了接受一個(gè)輸入?yún)?shù) T 并且無(wú)返回的操作 |
Function<T,R> | T | R | 接受一個(gè)輸入?yún)?shù) T ,返回一個(gè)結(jié)果 R |
UnaryOperator | T | T | 接受一個(gè)參數(shù)為類型 T ,返回值類型也為 T |
BinaryOperator | (T,T) | T | 代表了一個(gè)作用于于兩個(gè)同類型操作符的操作,并且返回了操作符同類型的結(jié)果 |
3.1 Predicate
條件判斷并返回一個(gè)Boolean值,包含一個(gè)抽象方法 (test) 和常見的三種邏輯關(guān)系 與 (and) 、或 (or) 、非 (negate) 的默認(rèn)方法。
Predicate 接口通過(guò)不同的邏輯組合能夠滿足我們常用的邏輯判斷的使用場(chǎng)景。
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("邏輯與的結(jié)果:"+isValid);
//邏輯或
isValid = doOr(s -> s.contains("H"),s-> s.contains("w"));
System.out.println("邏輯或的結(jié)果:"+isValid);
}
private static void doTest(Predicate<String> predicate) {
boolean veryLong = predicate.test("Hello World");
System.out.println("字符串長(zhǎng)度很長(zhǎng)嗎:" + 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("字符串長(zhǎng)度很長(zhǎng)嗎:" + veryLong);
}
}
結(jié)果如下:
字符串長(zhǎng)度很長(zhǎng)嗎:true
字符串長(zhǎng)度很長(zhǎng)嗎:false
邏輯與的結(jié)果:true
邏輯或的結(jié)果:true
3.2 Supplier
用來(lái)獲取一個(gè)泛型參數(shù)指定類型的對(duì)象數(shù)據(jù)(生產(chǎn)一個(gè)數(shù)據(jù)),我們可以把它理解為一個(gè)工廠類,用來(lái)創(chuàng)建對(duì)象。
Supplier 接口包含一個(gè)抽象方法 get
,通常我們它來(lái)做對(duì)象轉(zhuǎn)換。
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();
}
}
結(jié)果如下:
Hello World
上述例子中,我們把兩個(gè) String 對(duì)象合并成一個(gè) String。
3.3 Consumer
與 Supplier 接口相反,Consumer 接口用于消費(fèi)一個(gè)數(shù)據(jù)。
Consumer 接口包含一個(gè)抽象方法 accept
以及默認(rèn)方法 andThen
這樣 Consumer 接口可以通過(guò) andThen
來(lái)進(jìn)行組合滿足我們不同的數(shù)據(jù)消費(fèi)需求。最常用的 Consumer 接口就是我們的 for
循環(huán),for
循環(huán)里面的代碼內(nèi)容就是一個(gè) Consumer 對(duì)象的內(nèi)容。
import java.util.function.Consumer;
public class DemoConsumer {
public static void main(String[] args) {
//調(diào)用默認(rèn)方法
consumerString(s -> System.out.println(s));
//consumer接口的組合
consumerString(
// toUpperCase()方法,將字符串轉(zhuǎn)換為大寫
s -> System.out.println(s.toUpperCase()),
// toLowerCase()方法,將字符串轉(zhuǎn)換為小寫
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");
}
}
結(jié)果如下:
Hello
HELLO
hello
在調(diào)用第二個(gè) consumerString
的時(shí)候我們通過(guò) andThen
把兩個(gè) Consumer
組合起來(lái),首先把 Hello
全部轉(zhuǎn)變成大寫,然后再全部轉(zhuǎn)變成小寫。
3.4 Function
根據(jù)一個(gè)類型的數(shù)據(jù)得到另一個(gè)類型的數(shù)據(jù),換言之,根據(jù)輸入得到輸出。
Function 接口有一個(gè)抽象方法 apply
和默認(rèn)方法 andThen
,通過(guò) andThen
可以把多個(gè) Function
接口進(jìn)行組合,是適用范圍最廣的函數(shù)接口。
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);
}
}
結(jié)果如下:
30
220
上述四個(gè)函數(shù)接口是最基本最常用的函數(shù)接口,需要熟悉其相應(yīng)的使用場(chǎng)景并能夠熟練使用。 UnaryOperator
和 BinaryOperator
作用與 Funciton
類似,大家可以通過(guò) Java
的源代碼進(jìn)一步了解其作用。
4. 小結(jié)

本節(jié),我們主要闡述了函數(shù)式接口的定義以及其與 Lambda 表達(dá)式的關(guān)系。并對(duì)新增的 java.util.function
包中常用的函數(shù)式接口進(jìn)行了解釋。這些接口常用于集合處理中(我們將在后續(xù)的內(nèi)容進(jìn)一步學(xué)習(xí)),關(guān)于函數(shù)式接口主要記住一點(diǎn),那就是:
接口有且僅有一個(gè)抽象方法