stream and lambda(3) - jdk 提供的函數式接口

語言: CN / TW / HK

使用函數式接口開發,如果針對每個需要使用的場景都自定義一個函數式接口,會導致我們的項目中有很多僅僅當參數的接口。如果我們能自己抽離這些使用場景的共性,則會省去很多自定義函數式接口,相信很多人會遇到這種情況。幸運的是,JDK 已經提前幫我們把這一步做好了。

Java8 之前的函數式接口

Java8 之前是沒有函數式接口這個概念的,但是在 Java8 中,有些接口升級成了函數式接口。最常見的當屬以下三位選手:

1 . java.lang.Runnable 2 . java.util.concurrent.Callable 3 . java.util.Comparator

Java8 之後的函數式接口

Java8 提供了一系列的函數式接口,主要在 java.util.function 包目錄下。在瞭解這些函數式接口前,再默唸幾遍之前反覆提到的要點: 接口名不重要(反正也是匿名),方法名不重要(反正只有一個抽象方法),重要的是能幹什麼(行為)

接口 入參 返回類型 説明
Function<T,R> T R 輸入 T 返回 R,有輸入有輸出
Consumer<T> T / 純消費,只有輸入沒有輸出
Supplier<T> / T 純供應,沒有輸入只有輸出
Predicate<T> T boolean 斷言,輸出布爾類型
UnaryOperator<T> T T 一元函數,輸入輸出類型相同
BinaryOperator<T> (T,T) T 二元函數,輸入輸出類型相同

Function<T,R>

使用説明:當鋪,用東西換錢。

先看源碼

@FunctionalInterface
public interface Function<T, R> {

    // 入參類型為T,處理後,返回類型為R的結果。
    R apply(T t);

    // 入參為 Function 對象 before,返回一個 Function 對象 result。
    // 執行時,先執行一次 before.apply 方法的,再執行 result.apply
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    // 入參為 Function 對象 after,返回一個 Function 對象 result。
    // 執行時,先執行一次 result.apply 方法的,再執行 after.apply
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    // 靜態方法,返回一個只會返回入參的 Function 對象。
    // 相當於 t->t 的簡寫,主要用於返回 lambda 表達式
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

實踐

apply 方法,處理傳入的參數並返回。

public void doubleNum() {
    Function<Integer, String> d = integer -> String.valueOf(2 * integer);
    System.out.println(d.apply(10));
}

compose 方法,是傳入一個前置處理的函數,前置函數的返回結果是 apply 的入參。

public void compose() {
    Function<Integer, String> d = integer -> {
        System.out.println("real double apply begins, param is " + integer);
        return String.valueOf(2 * integer);
    };
    //先將輸入的字符串轉為Integer,再乘法。compose的入參可以指定,輸出一定要是apply的入參
    Function<String, String> compose = d.compose(s -> {
        System.out.println("compose begins, str to integer, str is " + s);
        return Integer.valueOf(s);
    });
    System.out.println("final result is " + compose.apply("10"));
}

andThen 方法,傳入一個後置函數,可以形成鏈式處理。

public void andThen() {
    Function<Integer, String> d = integer -> {
        System.out.println("real double apply begins, param is " + integer);
        return String.valueOf(2 * integer);
    };

    //andThen傳入的是apply的結果,處理後的類型可以指定
    Function<Integer, String> andThen = d.andThen(s -> {
        System.out.println("and then begins");
        return "after and then, result is " + s;
    });

    System.out.println(andThen.apply(10));
}

identity ,是 t -> t 的一種替代式寫法。

public void identify() {
    List<Student> list = new ArrayList<>();
    list.add(new Student("1", "張三"));
    list.add(new Student("2", "李四"));

    //兩種寫法等效的
    Map<String, Student> map1 = list.stream().collect(Collectors.toMap(Student::getId, s -> s));
    Map<String, Student> map2 = list.stream().collect(Collectors.toMap(Student::getId, Function.identity()));
}

其他類型 Function

接口 函數 説明
BiFunction R apply(T t, U u) 二元入參
DoubleFunction R apply(double value) 輸入為double
DoubleToIntFunction int applyAsInt(double value) 輸入double,輸出int
DoubleToLongFunction long applyAsLong(double value) 輸入double,輸入long
IntFunction R apply(int value) 輸入為int
IntToDoubleFunction double applyAsDouble(int value) 輸入int,輸出double
IntToLongFunction long applyAsLong(int value) 輸入int,輸出long
LongFunction R apply(long value) 輸入為long
LongToDoubleFunction double applyAsDouble(long value) 輸入long,輸出double
LongToIntFunction int applyAsInt(long value) 輸入long,輸出int
ToLongFunction long applyAsLong(T value) 輸出為long
ToLongBiFunction long applyAsLong(T t, U u) 二元入參,輸出為long
ToIntFunction int applyAsInt(T value) 輸出為int
ToIntBiFunction int applyAsInt(T t, U u) 二元入參,輸出為int
ToDoubleFunction double applyAsDouble(T value) 輸出為long
ToDoubleBiFunction double applyAsDouble(T t, U u) 二元入參,輸出為long

Consumer<T>

使用説明:貔貅,光進不出。

先看源碼

@FunctionalInterface
public interface Consumer<T> {
    //處理邏輯
    void accept(T t);

    //傳入一個後置函數
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

實踐

Consumer 主要是用於提供一個可以對入參處理的函數,比如用在 foreach

public static void main(String[] args) {
    Consumer<String> c1 = str -> System.out.println("this is the first time, word is " + str);
    Consumer<String> c2 = str -> System.out.println("this is the second time, word is " + str);
    List<String> stringList = Arrays.asList("I", "am", "the", "demo");

    stringList.forEach(c1);
    stringList.forEach(c1.andThen(c2));
}

其他類型 Consumer

接口 函數 説明
BiConsumer 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

Supplier<T>

使用説明:老好人,散財童子,啥都不要。

先看源碼

Supplier 的源碼就比較簡單了,只有一個 get 方法,返回指定類型對象。

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

實踐

public void demo() {
    Supplier<Double> random = Math::random;
    System.out.println(random.get());

    Supplier<String> s = String::new;
    String result = Stream.of("this", "is", "is", "a", "demo").findFirst().orElseGet(s);
    System.out.println(result);
}

其他類型 Supplier

接口 函數 説明
BooleanSupplier boolean getAsBoolean() 返回boolean
DoubleSupplier double getAsDouble() 返回double
IntSupplier int getAsInt() 返回int
LongSupplier long getAsLong() 返回long

Predicate<T>

使用説明:預言家,判斷你是不是狼人。

先看源碼

@FunctionalInterface
public interface Predicate<T> {

    //業務判斷邏輯
    boolean test(T t);
    //and 邏輯
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    //取非邏輯
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    //or 邏輯
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    //靜態方法,返回一個Predicate函數,用於判斷兩個對象是否相等
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

實踐

Predicate 主要用於有布爾類型的場景。

public static void demo() {
    Predicate<Integer> p1 = integer -> integer > 10;
    Predicate<Integer> p2 = integer -> integer < 5;

    System.out.println("7 > 10 :" + p1.test(7));
    System.out.println("7 !< 5 :" + p2.negate().test(7));
    System.out.println("7 > 10 && 7 < 5 : " + p1.and(p2).test(7));
    System.out.println("0 > 10 || 0 < 5 : " + p1.or(p2).test(0));
    System.out.println("null equals null : " + Predicate.isEqual(null).test(null));
}

其他類型 Predicate

接口 函數 説明
BiPredicate boolean test(T t, U u) 二元入參
DoublePredicate boolean test(double value) 入參double
IntPredicate boolean test(int value) 入參返回int
LongPredicate boolean test(long value) 入參返回long

UnaryOperator<T>

使用説明:匯率結算,用錢換錢。

先看源碼

UnaryOperator 繼承 Function ,只不過是輸入輸出類型相同。

public interface UnaryOperator<T> extends Function<T, T> {
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

實踐

public void unaryDemo() {
    Function<Integer, Integer> squareFunc = integer -> integer * integer;
    UnaryOperator<Integer> squareUnary = integer -> integer * integer;

    System.out.println(squareFunc.apply(9));
    System.out.println(squareUnary.apply(9));
}

BinaryOperator<T>

使用説明:合成機器,兩個寶石合成一個寶石。

先看源碼

BinaryOperator 繼承自 BiFunction ,只不過是兩個入參以及返回類型相同。

同時, BinaryOperator 提供了 minBymaxBy 兩個靜態方法。

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {

    //根據比較器,返回較小的那個
    public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
    }

    //根據比較器,返回較大的那個
    public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
    }
}

實踐

比如整數四則運算,兩個輸入和輸出是一樣的類型。

public void multiply() {
    BinaryOperator<Integer> multiply = (i1, i2) -> i1 * i2;
    System.out.println(multiply.apply(3, 5));
}

minBymaxBy ,按比較器輸出。

public void compare() {
    BinaryOperator<Integer> min = BinaryOperator.minBy(Comparator.comparingInt(i -> i));
    BinaryOperator<Integer> max = BinaryOperator.maxBy(Comparator.comparingInt(i -> i));
    System.out.println(min.apply(1,2));
    System.out.println(max.apply(1,2));
}

總結

函數式接口,歸根究底還是接口,定義的是標準行為。但是函數式接口又只有一個抽象函數,使得我們可以不關注函數名字到底是什麼,而專注於業務邏輯。

JDK 提供的這一套函數式接口,全部在 java.util.function 包中,基本上可以滿足我們常用場景的使用需求。這些接口有些抽象,但只要將關注點轉移到這個接口到底能幹什麼,就不再難理解了。當然,如果還有特殊的使用場景,可以通過自定義函數式接口解決。

注:本文配套代碼可在 github 查看: stream-and-lambda