前言
在上一篇Lambda的講解中我們就提到過函式式介面,比如:Consumer<String>
consumer = (s) -> System.out.println(s);其中Consumer就是一個函式式介面。這裡是通過Lambda表示式建立了一個函式式介面的物件。如果不知道什麼是Lambda,請看《神祕的Lambda》。
函式式介面是什麼?
有且只有一個抽象方法的介面被稱為函式式介面,函式式介面適用於函數語言程式設計的場景,Lambda就是Java中函數語言程式設計的體現,可以使用Lambda表示式建立一個函式式介面的物件,一定要確保介面中有且只有一個抽象方法,這樣Lambda才能順利的進行推導。
@FunctionalInterface註解
與@Override 註解的作用類似,Java 8中專門為函式式介面引入了一個新的註解:@FunctionalInterface 。該註解可用於一個介面的定義上,一旦使用該註解來定義介面,編譯器將會強制檢查該介面是否確實有且僅有一個抽象方法,否則將會報錯。但是這個註解不是必須的,只要符合函式式介面的定義,那麼這個介面就是函式式介面。
static方法和default方法
實在不知道該在哪介紹這兩個方法了,所以就穿插在這裡了。
static方法:
java8中為介面新增了一項功能,定義一個或者多個靜態方法。用法和普通的static方法一樣,例如:
public interface Interface {
/**
* 靜態方法
*/
static void staticMethod() {
System.out.println("static method");
}
}
複製程式碼
注意:實現介面的類或者子介面不會繼承介面中的靜態方法。
default方法:
java8在介面中新增default方法,是為了在現有的類庫中中新增功能而不影響他們的實現類,試想一下,如果不增加預設實現的話,介面的所有實現類都要實現一遍這個方法,這會出現相容性問題,如果定義了預設實現的話,那麼實現類直接呼叫就可以了,並不需要實現這個方法。default方法怎麼定義?
public interface Interface {
/**
* default方法
*/
default void print() {
System.out.println("hello default");
}
}
複製程式碼
注意:如果介面中的預設方法不能滿足某個實現類需要,那麼實現類可以覆蓋預設方法。不用加default關鍵字,
例如:
public class InterfaceImpl implements Interface {
@Override
public void print() {
System.out.println("hello default 2");
}
}
複製程式碼
在函式式介面的定義中是隻允許有一個抽象方法,但是可以有多個static方法和default方法。
自定義函式式介面
按照下面的格式定義,你也能寫出函式式介面:
@FunctionalInterface
修飾符 interface 介面名稱 {
返回值型別 方法名稱(可選引數資訊);
// 其他非抽象方法內容
}
複製程式碼
雖然@FunctionalInterface註解不是必須的,但是自定義函式式介面最好還是都加上,一是養成良好的程式設計習慣,二是防止他人修改,一看到這個註解就知道是函式式介面,避免他人往介面內新增抽象方法造成不必要的麻煩。
@FunctionalInterface
public interface MyFunction {
void print(String s);
}
複製程式碼
看上圖是我自定義的一個函式式介面,那麼這個介面的作用是什麼呢?就是輸出一串字串,屬於消費型介面,是模仿Consumer介面寫的,只不過這個沒有使用泛型,而是將引數具體型別化了,不知道Consumer沒關係,下面會介紹到,其實java8中提供了很多常用的函式式介面,Consumer就是其中之一,一般情況下都不需要自己定義,直接使用就好了。那麼怎麼使用這個自定義的函式式介面呢?我們可以用函式式介面作為引數,呼叫時傳遞Lambda表示式。如果一個方法的引數是Lambda,那麼這個引數的型別一定是函式式介面。例如:
public class MyFunctionTest {
public static void main(String[] args) {
String text = "試試自定義函式好使不";
printString(text, System.out::print);
}
private static void printString(String text, MyFunction myFunction) {
myFunction.print(text);
}
}
複製程式碼
執行以後就會輸出“試試自定義函式好使不”這句話,如果某天需求變了,我不想輸出這句話了,想輸出別的,那麼直接替換text就好了。函數語言程式設計是沒有副作用的,最大的好處就是函式的內部是無狀態的,既輸入確定輸出就確定。函數語言程式設計還有更多好玩的套路,這就需要靠大家自己探索了。?
常用函式式介面
Consumer<T>
:消費型介面
抽象方法: void accept(T t),接收一個引數進行消費,但無需返回結果。
使用方式:
Consumer consumer = System.out::println;
consumer.accept("hello function");
複製程式碼
預設方法: andThen(Consumer<? super T> after),先消費然後在消費,先執行呼叫andThen介面的accept方法,然後在執行andThen方法引數after中的accept方法。
使用方式:
Consumer<String> consumer1 = s -> System.out.print("車名:"+s.split(",")[0]);
Consumer<String> consumer2 = s -> System.out.println("-->顏色:"+s.split(",")[1]);
String[] strings = {"保時捷,白色", "法拉利,紅色"};
for (String string : strings) {
consumer1.andThen(consumer2).accept(string);
}
複製程式碼
輸出: 車名:保時捷-->顏色:白色 車名:法拉利-->顏色:紅色
Supplier<T>
: 供給型介面
抽象方法:T get(),無引數,有返回值。
使用方式:
Supplier<String> supplier = () -> "我要變的很有錢";
System.out.println(supplier.get());
複製程式碼
最後輸出就是“我要變得很有錢”,這類介面適合提供資料的場景。
Function<T,R>
: 函式型介面
抽象方法: R apply(T t),傳入一個引數,返回想要的結果。
使用方式:
Function<Integer, Integer> function1 = e -> e * 6;
System.out.println(function1.apply(2));
複製程式碼
很簡單的一個乘法例子,顯然最後輸出是12。
預設方法:
- compose(Function<? super V, ? extends T> before),先執行compose方法引數before中的apply方法,然後將執行結果傳遞給呼叫compose函式中的apply方法在執行。
使用方式:
Function<Integer, Integer> function1 = e -> e * 2;
Function<Integer, Integer> function2 = e -> e * e;
Integer apply2 = function1.compose(function2).apply(3);
System.out.println(apply2);
複製程式碼
還是舉一個乘法的例子,compose方法執行流程是先執行function2的表示式也就是33=9,然後在將執行結果傳給function1的表示式也就是92=18,所以最終的結果是18。
- andThen(Function<? super R, ? extends V> after),先執行呼叫andThen函式的apply方法,然後在將執行結果傳遞給andThen方法after引數中的apply方法在執行。它和compose方法整好是相反的執行順序。
使用方式:
Function<Integer, Integer> function1 = e -> e * 2;
Function<Integer, Integer> function2 = e -> e * e;
Integer apply3 = function1.andThen(function2).apply(3);
System.out.println(apply3);
複製程式碼
這裡我們和compose方法使用一個例子,所以是一模一樣的例子,由於方法的不同,執行順序也就不相同,那麼結果是大大不同的。andThen方法是先執行function1表示式,也就是32=6,然後在執行function2表示式也就是66=36。結果就是36。 **靜態方法:**identity(),獲取一個輸入引數和返回結果相同的Function例項。
使用方式:
Function<Integer, Integer> identity = Function.identity();
Integer apply = identity.apply(3);
System.out.println(apply);
複製程式碼
平常沒有遇到過使用這個方法的場景,總之這個方法的作用就是輸入什麼返回結果就是什麼。
Predicate<T>
: 斷言型介面
抽象方法: boolean test(T t),傳入一個引數,返回一個布林值。
使用方式:
Predicate<Integer> predicate = t -> t > 0;
boolean test = predicate.test(1);
System.out.println(test);
複製程式碼
當predicate函式呼叫test方法的時候,就會執行拿test方法的引數進行t -> t > 0的條件判斷,1肯定是大於0的,最終結果為true。
預設方法:
- and(Predicate<? super T> other),相當於邏輯運算子中的&&,當兩個Predicate函式的返回結果都為true時才返回true。
使用方式:
Predicate<String> predicate1 = s -> s.length() > 0;
Predicate<String> predicate2 = Objects::nonNull;
boolean test = predicate1.and(predicate2).test("&&測試");
System.out.println(test);
複製程式碼
- or(Predicate<? super T> other) ,相當於邏輯運算子中的||,當兩個Predicate函式的返回結果有一個為true則返回true,否則返回false。
使用方式:
Predicate<String> predicate1 = s -> false;
Predicate<String> predicate2 = Objects::nonNull;
boolean test = predicate1.and(predicate2).test("||測試");
System.out.println(test);
複製程式碼
- negate(),這個方法的意思就是取反。
使用方式:
Predicate<String> predicate = s -> s.length() > 0;
boolean result = predicate.negate().test("取反");
System.out.println(result);
複製程式碼
很明顯正常執行test方法的話應該為true,但是呼叫negate方法後就返回為false了。 **靜態方法:**isEqual(Object targetRef),對當前操作進行"="操作,即取等操作,可以理解為 A == B。
使用方式:
boolean test1 = Predicate.isEqual("test").test("test");
boolean test2 = Predicate.isEqual("test").test("equal");
System.out.println(test1); //true
System.out.println(test2); //false
複製程式碼
其他函式式介面
Bi型別介面
BiConsumer、BiFunction、BiPrediate 是 Consumer、Function、Predicate 的擴充套件,可以傳入多個引數,沒有 BiSupplier 是因為 Supplier 沒有入參。
操作基本資料型別的介面
IntConsumer、IntFunction、IntPredicate、IntSupplier、LongConsumer、LongFunction、LongPredicate、LongSupplier、DoubleConsumer、DoubleFunction、DoublePredicate、DoubleSupplier。 其實常用的函式式介面就那四大介面Consumer、Function、Prediate、Supplier,其他的函式式介面就不一一列舉了,有興趣的可以去java.util.function這個包下詳細的看。
大家看后辛苦點個贊點個關注哦!後續還會後更多的部落格。想來想去建了個群,大家可以掃碼加群,一起學習、共同進步。有興趣可以掃碼加群。如有錯誤,煩請指正。