「Java8系列」神奇的函數式接口

前言

在上一篇Lambda的講解中咱們就提到過函數式接口,好比:Consumer<String> consumer = (s) -> System.out.println(s);其中Consumer就是一個函數式接口。這裏是經過Lambda表達式建立了一個函數式接口的對象。若是不知道什麼是Lambda,請看《神祕的Lambda》。java

函數式接口是什麼?

有且只有一個抽象方法的接口被稱爲函數式接口,函數式接口適用於函數式編程的場景,Lambda就是Java中函數式編程的體現,可使用Lambda表達式建立一個函數式接口的對象,必定要確保接口中有且只有一個抽象方法,這樣Lambda才能順利的進行推導。編程

@FunctionalInterface註解

與@Override 註解的做用相似,Java 8中專門爲函數式接口引入了一個新的註解:@FunctionalInterface 。該註解可用於一個接口的定義上,一旦使用該註解來定義接口,編譯器將會強制檢查該接口是否確實有且僅有一個抽象方法,不然將會報錯。可是這個註解不是必須的,只要符合函數式接口的定義,那麼這個接口就是函數式接口。bash

static方法和default方法

實在不知道該在哪介紹這兩個方法了,因此就穿插在這裏了。app

static方法:

java8中爲接口新增了一項功能,定義一個或者多個靜態方法。用法和普通的static方法同樣,例如:ide

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關鍵字,例如:post

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

你們看后辛苦點個贊點個關注哦!後續還會後更多的博客。想來想去建了個羣,你們能夠掃碼加羣,一塊兒學習、共同進步。有興趣能夠掃碼加羣。若有錯誤,煩請指正。

相關文章
相關標籤/搜索