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