「Java8系列」神奇的函式式介面

雙哥發表於2019-07-18

前言

在上一篇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這個包下詳細的看。

大家看后辛苦點個贊點個關注哦!後續還會後更多的部落格。想來想去建了個群,大家可以掃碼加群,一起學習、共同進步。有興趣可以掃碼加群。如有錯誤,煩請指正。

「Java8系列」神奇的函式式介面

相關文章