【睡JDK】Java函數式編程接口詳解之Consumer、Function

今天將介紹Java另外兩個函數編程接口Consumer、Function,這兩個函數是幹嘛的呢?先看看官方的定義:

  • Consumer:表示接受單個輸入參數但不返回結果的操作。
  • Function:表示接受一個參數並生成結果的函數。

一、Consumer

1.1 源代碼

@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); };
    }
}

1.2 混臉熟

其實Consumer我們經常使用,你看下面這個例子:

List<String> list = Arrays.asList("1", "2", "3");
list.forEach(System.out::println);

我們經常使用的forEach函數其實就是通過Consumer來實現的,所以掌握Consumer很有必要哦,下面看看forEach在ArrayList中的實現:

public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    
    final E[] elementData = (E[]) this.elementData;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

不用介紹,想必大家也能看的懂,Consumer就表示一個自定義的操作,將該操作作爲參數傳入到另一個函數內,可在該函數內執行自定義的操作。上面的for循環代碼就等同於:

for (int i=0; modCount == expectedModCount && i < size; i++) {
    System.out.println(elementData[i]);
}

1.3 實現

如果操作比較常用或者通用,可以使用一個類去實現Consumer,保存該操作,在必要的時候能快速使用。

// 實體類
public class Person {
    private String name;
    private Integer age;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    //...
}
// 打印小孩
public class PrintChild implements Consumer<Person> {
    @Override
    public void accept(Person person) {
        if (person.getAge() < 18) {
            System.out.println(person.getName() + "還是個孩子啊");
        }
    }
}

// 測試代碼
List<Person> list1 = Arrays.asList(
                new Person("大壯", 19),
                new Person("小光", 16),
                new Person("小小", 15));
list1.forEach(new PrintChild());

1.4 andThen函數介紹

函數源碼:

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

andThen函數的功能就是將兩個Consumer操作合併,並返回一個新的Consumer,使用如下:

// 打印年齡
public class PrintAge implements Consumer<Person> {
    @Override
    public void accept(Person person) {
        System.out.println(person.getName() + "的年齡是:" + person.getAge() + "歲");
    }
}

// 測試代碼
List<Person> list1 = Arrays.asList(
                new Person("小雨", 19),
                new Person("小光", 16),
                new Person("小小", 15));
list1.forEach(new PrintChild().andThen(new PrintAge()));

// 結果
小雨的年齡是:19歲
小光是個孩子
小光的年齡是:16歲
小小是個孩子
小小的年齡是:15

1.5 Consumer的其他變體

接口名 參數 返回類型 描述
BiConsumer (T, U) void BiConsumer接受兩個參數
DoubleConsumer double void 接受一個double類型的參數
IntConsumer int void 接受一個int類型的參數
LongConsumer long void 接受一個long類型的參數
ObjDoubleConsumer (T, double) void 接受一個Object類型和一個double類型參數
ObjIntConsumer (T, int) void 接受一個Object類型和一個int類型參數
ObjLongConsumer (T, long) void 接受一個Object類型和一個long類型參數

二、Function

Function和Consumer在功能上是一致的,但是Function有返回結果。

2.1 源代碼

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

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

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

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

2.2 混臉熟

Function我們也是會經常使用到的,喏:

List<Person> list1 = Arrays.asList(
                new Person("小雨", 19),
                new Person("小光", 16),
                new Person("小小", 15));
List<String> list = list1.stream()
    .map(Person::getName)
    .collect(Collectors.toList());

如果你看過筆者寫的另一篇文章(重識Java8函數式編程),那麼下面的代碼你應該也能看得懂,其實上面的map操作可以還原成:

List<String> list = list1.stream()
                .map(person -> {
                    return person.getName();
                })
                .collect(Collectors.toList());

map內就是封裝了一個有返回值的函數,將函數作爲參數參入map內。

2.3 compose函數介紹

函數源碼:

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

該函數作用就是組合兩個Function,參數Function before先執行,執行後的結果交由調用方Function執行。

// 加法
public class AddFunc implements Function<Integer, Integer> {
    private int origin;

    public AddFunc(int origin) {
        this.origin = origin;
    }

    @Override
    public Integer apply(Integer integer) {
        return this.origin + integer;
    }
}

// 減法
public class ReduceFunc implements Function<Integer, Integer> {
    private int origin;
    private boolean isMinuend;// origin被減數與否

    public ReduceFunc(int origin, boolean isMinuend) {
        this.origin = origin;
        this.isMinuend = isMinuend;
    }

    @Override
    public Integer apply(Integer integer) {
        return isMinuend ? this.origin - integer : integer - this.origin;
    }
}

// 測試代碼
public class Test {
    public static void main(String[] args) {
        // 計算 1 + (2 - 3)
        System.out.println(handle(new AddFunc(1).compose(new ReduceFunc(2, true)), 3));
    }

    public static int handle(Function<Integer, Integer> function, Integer integer) {
        return function.apply(integer);
    }
}

2.4 andThen函數介紹

函數源碼:

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

andThen函數的作用也是組合兩個Function,只不過參數Function後執行。

// 測試代碼
public class Test {
    public static void main(String[] args) {
        // 計算 1 + 2 - 4
        System.out.println(handle(new AddFunc(1).andThen(new ReduceFunc(4, false)), 2));
    }

    public static int handle(Function<Integer, Integer> function, Integer integer) {
        return function.apply(integer);
    }
}

2.5 identity函數

源碼:

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

好吧,這個函數的功能就是返回一個輸入和輸出都一樣的Function,那麼這個函數有什麼用呢?看示例:

// 示例:將list轉變爲map
List<Person> list = Arrays.asList(
                new Person("xiaoxiao", 11),
                new Person("dazhuang", 15));
Map<String, Person> map = list.stream()
    .collect(Collectors.toMap(Person::getName, person -> person));

// 使用identity函數
List<Person> list = Arrays.asList(
                new Person("xiaoxiao", 11),
                new Person("dazhuang", 15));
Map<String, Person> map = list.stream()
    .collect(Collectors.toMap(Person::getName, Function.identity()));

兩種使用方式都是可以的,但是你覺得哪個方式更有魅(bi)力(ge)呢?

2.6 我們能做啥

  • 掌握了Function,在編寫公共組件邏輯的時候,你可以將部分邏輯上拋,由調用者去實現自己的特性,能增強組件的靈活性。
  • 實現Function接口,定義一些常用操作,減少代碼的冗餘。

運用的基礎是先掌握,只有掌握才能熟練運用,乾了這杯毒雞湯。(小聲BB:端午最後一天假了,T . T)

2.7 Function的其他變體

接口名 參數 返回類型 描述
BiFunction (T, U) R BiFunction接受兩個參數
DoubleFunction double R 接受一個double類型的參數
DoubleToIntFunction double int 接受double類型參數,返回int類型結果
DoubleToLongFunction double long 接受double類型參數,返回long類型結果
IntFunction int R 接受一個int類型的參數
IntToDoubleFunction int double 接受int類型參數,返回double類型結果
IntToLongFunction int long 接受int類型參數,返回long類型結果
LongFunction long R 接受一個long類型的參數
LongToDoubleFunction long double 接受long類型參數,返回double類型結果
LongToIntFunction long int 接受long類型參數,返回int類型結果
ToDoubleBiFunction (T, U) double 接受兩個參數,返回double類型結果
ToDoubleFunction T double 接受一個Object類型,返回double類型參數
ToIntBiFunction (T, U) int 接受兩個參數,返回int類型結果
ToIntFunction T int 接受一個Object類型,返回int類型參數
ToLongBiFunction (T, U) long 接受兩個參數,返回long類型結果
ToLongFunction T long 接受一個Object類型,返回long類型參數

文章推薦:

【睡JDK】Java函數式編程接口詳解之Predicate

【睡JDK】Java函數式編程接口詳解之Supplier

【睡JDK】Java函數式編程接口詳解之UnaryOperator、BinaryOperator

end
Java開發樂園

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