【小家java】java8新特性之---函數式接口(Supplier、Consumer、Predicate、Function、UnaryOperator,通往高階設計的好工具)

相關閱讀

【小家java】java5新特性(簡述十大新特性) 重要一躍
【小家java】java6新特性(簡述十大新特性) 雞肋升級
【小家java】java7新特性(簡述八大新特性) 不溫不火
【小家java】java8新特性(簡述十大新特性) 飽受讚譽
【小家java】java9新特性(簡述十大新特性) 褒貶不一
【小家java】java10新特性(簡述十大新特性) 小步迭代


【小家java】java8新特性之—Base64加密和解密原理
【小家java】java8新特性之—反射獲取方法參數名
【小家java】java8新特性之—全新的日期、時間API(完全實現了JSR 310規範)
【小家java】java8新特性之—Optional的使用,避免空指針,代替三目運算符
【小家java】java8新特性之—lambda表達式的的原理
【小家java】java8新特性之—函數式接口(Supplier、Consumer、Predicate、Function、UnaryOperator,通往高階設計的好工具)
【小家java】java8新特性之—方法引用
【小家java】java8新特性之—Stream API 詳解 (Map-reduce、Collectors收集器、並行流)
【小家java】java8新特性之—外部迭代和內部迭代(對比性能差異)


什麼是函數式接口?

所有函數式接口都在這個包:java.util.function

首先,它還是一個接口,所以必須滿足接口最基本的定義。但它是一個特殊的接口:SAM類型的接口(Single Abstract Method)。可以在調用時,使用一個lambda表達式作爲參數。
定義要求:

  1. 只能有一個抽象方法需要被實現
@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}

備註:此處不包括與Object的public方法(clone方法不行,因爲clone方法是protected,編譯會報錯)重名的方法。當然裏面的默認方法、static方法都是無所謂的

default 修飾的默認方法方法,這個關鍵字是Java8中新增的,爲的目的就是使得某一些接口,原則上只有一個方法被實現,但是由於歷史原因,不得不加入一些方法來兼容整個JDK中的API,所以就需要使用default關鍵字來定義這樣的方法

  1. 可以有從Object繼承過來的抽象方法,因爲所有類的最終父類都是Object
  2. 接口中唯一抽象方法的命名並不重要,因爲函數式接口就是對某一行爲進行抽象,主要目的就是支持Lambda表達式。

以下附JDK 8之前已有的函數式接口:
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener

Java8還提供了@FunctionalInterface註解來幫助我們標識函數式接口。所以Java8後上面那些接口都被打上了這個標記。

下面給出一張圖:說出Java8新提供的函數式接口們(可以滿足99%需求):
這裏寫圖片描述
四大核心函數式接口:
這裏寫圖片描述

public interface Supplier

其簡潔的聲明,會讓人以爲不是函數。這個抽象方法的聲明,同Consumer相反,是一個只聲明瞭返回值,不需要參數的函數(這還叫函數?)。也就是說Supplier其實表達的不是從一個參數空間到結果空間的映射能力,而是表達一種生成能力。

Supplier<String> supplier = String::new;

其他Supplier擴展接口:

  • BooleanSupplier:boolean getAsBoolean();返回boolean
  • DoubleSupplier:double getAsDouble();返回double
  • IntSupplier:int getAsInt();返回int
  • LongSupplier:long getAsLong();返回long
public interface Consumer

這個接口聲明太重要了,應用場景太多了。因爲需要返回值的我們用Function,不需要返回值的,我們用它就可。

Consumer consumer = System.out::println;

看其源碼 還有個默認方法andThen:

void accept(T t);
 default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }

andThen可以實現消費兩次。消費一次後,繼續消費一次。使用場景:

其他Consumer擴展接口:

  • BiConsumer:void accept(T t, U u);接受兩個參數
  • DoubleConsumer:void accept(double value);接受一個double參數
  • IntConsumer:void accept(int value);接受一個int參數
  • LongConsumer:void accept(long value);接受一個long參數
  • ObjDoubleConsumer:void accept(T t, double value);接受一個泛型參數一個double參數
  • ObjIntConsumer:void accept(T t, int value);接受一個泛型參數一個int參數
  • ObjLongConsumer:void accept(T t, long value);接受一個泛型參數一個long參數
public interface Predicate

斷言接口,有點意思了。其默認方法也封裝了and、or和negate邏輯 和一個靜態方法isEqual。

//and方法接收一個Predicate類型,也就是將傳入的條件和當前條件以並且的關係過濾數據。
default Predicate<T> and(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) -> test(t) && other.test(t);
}

//or方法同樣接收一個Predicate類型,將傳入的條件和當前的條件以或者的關係過濾數據
default Predicate<T> or(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) -> test(t) || other.test(t);
}

//negate就是將當前條件取反
default Predicate<T> negate() {
    return (t) -> !test(t);
}

static <T> Predicate<T> isEqual(Object targetRef) {
    return (null == targetRef)
            ? Objects::isNull
            : object -> targetRef.equals(object);
}

看幾個案例:

public List<Integer> conditionFilterAnd(List<Integer> list, Predicate<Integer> predicate,Predicate<Integer> predicate2){
    return list.stream().filter(predicate.and(predicate2)).collect(Collectors.toList());
}

public List<Integer> conditionFilterOr(List<Integer> list, Predicate<Integer> predicate,Predicate<Integer> predicate2){
    return list.stream().filter(predicate.or(predicate2)).collect(Collectors.toList());
}
public List<Integer> conditionFilterNegate(List<Integer> list, Predicate<Integer> predicate){
    return list.stream().filter(predicate.negate()).collect(Collectors.toList());
}

//大於5並且是偶數
result = predicateTest.conditionFilterAnd(list, integer -> integer > 5, integer1 -> integer1 % 2 == 0);
result.forEach(System.out::println);//6 8 10
System.out.println("-------");

//大於5或者是偶數
result = predicateTest.conditionFilterOr(list, integer -> integer > 5, integer1 -> integer1 % 2 == 0);
result.forEach(System.out::println);//2 4 6 8 9 10
System.out.println("-------");

//條件取反
result = predicateTest.conditionFilterNegate(list,integer2 -> integer2 > 5);
result.forEach(System.out::println);// 1 2 3 4 5
System.out.println("-------");

最後再來看一下Predicate接口中的唯一一個靜態方法(小縱範圍使用):

isEqual方法返回類型也是Predicate,也就是說通過isEqual方法得到的也是一個用來進行條件判斷的函數式接口實例。而返回的這個函數式接口實例是通過傳入的targetRef的equals方法進行判斷的。我們看一下具體

public static void main(String[] args) {
        System.out.println(Predicate.isEqual("test").test("test")); //true
        System.out.println(Predicate.isEqual(null).test("test")); //false
        System.out.println(Predicate.isEqual(null).test(null)); //true
        System.out.println(Predicate.isEqual(1).test(new Integer(1))); //true
        //注意 這裏是false的
        System.out.println(Predicate.isEqual(new Long(1)).test(new Integer(1))); //false
    }

其他Predicate擴展接口:

  • BiPredicate:boolean test(T t, U u);接受兩個參數的,判斷返回bool
  • DoublePredicate:boolean test(double value);入參爲double的謂詞函數
  • IntPredicate:boolean test(int value);入參爲int的謂詞函數
  • LongPredicate:boolean test(long value);入參爲long的謂詞函數
public interface Function

這個接口非常非常總要。是很上層的一個抽象。除了一個抽象方法apply外,其默認實現了3個default方法,分別是compose、andThen和identity。

  default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

 default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

compose 和 andThen 的不同之處是函數執行的順序不同。andThen就是按照正常思維:先執行調用者,再執行入參的。然後compose 是反着來的,這點需要注意。

看看唯一的一個靜態方法identity:

static <T> Function<T, T> identity() {
        return t -> t;
    }

我們會發現,identity啥都沒做,只是返回了一個Function方法,並且是兩個泛型都一樣的方法,意義着實不是太大。下面看一個複雜點的例子,各位感受一下:

 public static void main(String[] args) {
        Function<Integer, Integer> times2 = i -> i * 2; //加倍函數
        Function<Integer, Integer> squared = i -> i * i; //平方函數

        System.out.println(times2.apply(4)); //8
        System.out.println(squared.apply(4)); //16

        System.out.println(times2.compose(squared).apply(4));  //32   先4×4然後16×2, 先執行參數,再執行調用者
        System.out.println(times2.andThen(squared).apply(4));  //64   先4×2,然後8×8, 先執行調用者,再執行參數

        //看看這個例子Function.identity()構建出一個恆等式函數而已,方便方法的連綴 這就是它的唯一優點
        System.out.println(Function.identity().compose(squared).apply(4));   //16 先執行4*4,再執行identity 值不變
    }

由Function,可以擴展出高階函數。如泛型中有個類型還是Function,這種需要還是經常有的,所以BiFunction提供了二元函數的一個接口聲明

  public static void main(String[] args) {
        BiFunction<Integer, Integer, Integer> biFunction = (x, y) -> x + y;
        System.out.println(biFunction.apply(4, 5)); //9
        System.out.println(biFunction.andThen(x -> x + 10).apply(4, 5)); //19
    }

二元函數沒有compose能力,只是默認實現了andThen。有了一元和二元函數,那麼可以通過組合擴展出更多的函數可能。

Function相關擴展接口:

  • BiFunction :R apply(T t, U u);接受兩個參數,返回一個值,代表一個二元函數;
  • DoubleFunction :R apply(double value);只處理double類型的一元函數;
  • ToDoubleFunction:double applyAsDouble(T value);返回double的一元函數;
  • ToDoubleBiFunction:double applyAsDouble(T t, U u);返回double的二元函數;
  • IntToLongFunction:long applyAsLong(int value);接受int返回long的一元函數;

裏面有很多關於int、long、double的一元二元函數,這裏就不一一例舉了。

Operator

Operator其實就是Function,函數有時候也叫作算子。算子在Java8中接口描述更像是函數的補充,和上面的很多類型映射型函數類似。它包含UnaryOperator和BinaryOperator。分別對應單元算子和二元算子。

UnaryOperator
 @FunctionalInterface
    public interface UnaryOperator<T> extends Function<T, T> {
        static <T> java.util.function.UnaryOperator<T> identity() {
            return t -> t;
        }
    }
  @FunctionalInterface
    public interface BinaryOperator<T> extends BiFunction<T,T,T> {
        public static <T> java.util.function.BinaryOperator<T> minBy(Comparator<? super T> comparator) {
            Objects.requireNonNull(comparator);
            return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
        }
        public static <T> java.util.function.BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
            Objects.requireNonNull(comparator);
            return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
        }
    }

很明顯,算子就是一個針對同類型輸入輸出的一個映射。在此接口下,只需聲明一個泛型參數T即可。直接上例子,就非常清楚使用場景了:

  public static void main(String[] args) {
        UnaryOperator<Integer> unaryOperator = x -> x + 10;
        BinaryOperator<Integer> binaryOperator = (x, y) -> x + y;

        System.out.println(unaryOperator.apply(10)); //20
        System.out.println(binaryOperator.apply(5, 10)); //15

        //繼續看看BinaryOperator提供的兩個靜態方法   也挺好用的
        BinaryOperator<Integer> min = BinaryOperator.minBy(Integer::compare);
        BinaryOperator<Integer> max = BinaryOperator.maxBy(Integer::compareTo);
        System.out.println(min.apply(10, 20)); //10
        System.out.println(max.apply(10, 20)); //20
    }

BinaryOperator提供了兩個默認的static快捷實現,幫助實現二元函數min(x,y)和max(x,y),使用時注意的是排序器可別傳反了:)

提示一個小點:compareTo是Integer的實例方法,而compare是靜態方法。其實1.8之後,Interger等都提供了min、max、sum等靜態方法

其他的Operator接口:(不解釋了)

  • LongUnaryOperator:long applyAsLong(long operand);
  • LongBinaryOperator:long applyAsLong(long left, long right);
  • 。。。省略不寫了

最後

我們會發現,JDK的設計還是很有規律的。每個函數式接口對基本數據類型的中的int、long、double都提供了對應的擴展接口。可我們是否想過?爲什麼別的數據基本類型沒有對應接口呢?這個我在Stream那一章有說明原因,各位看官可跳轉到那裏觀看

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章