【睡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)voidBiConsumer接受两个参数
DoubleConsumerdoublevoid接受一个double类型的参数
IntConsumerintvoid接受一个int类型的参数
LongConsumerlongvoid接受一个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)RBiFunction接受两个参数
DoubleFunctiondoubleR接受一个double类型的参数
DoubleToIntFunctiondoubleint接受double类型参数,返回int类型结果
DoubleToLongFunctiondoublelong接受double类型参数,返回long类型结果
IntFunctionintR接受一个int类型的参数
IntToDoubleFunctionintdouble接受int类型参数,返回double类型结果
IntToLongFunctionintlong接受int类型参数,返回long类型结果
LongFunctionlongR接受一个long类型的参数
LongToDoubleFunctionlongdouble接受long类型参数,返回double类型结果
LongToIntFunctionlongint接受long类型参数,返回int类型结果
ToDoubleBiFunction(T, U)double接受两个参数,返回double类型结果
ToDoubleFunctionTdouble接受一个Object类型,返回double类型参数
ToIntBiFunction(T, U)int接受两个参数,返回int类型结果
ToIntFunctionTint接受一个Object类型,返回int类型参数
ToLongBiFunction(T, U)long接受两个参数,返回long类型结果
ToLongFunctionTlong接受一个Object类型,返回long类型参数

文章推荐:

【睡JDK】Java函数式编程接口详解之Predicate

【睡JDK】Java函数式编程接口详解之Supplier

【睡JDK】Java函数式编程接口详解之UnaryOperator、BinaryOperator

end
Java开发乐园

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JDK 1.8中,函数式接口Functional Interface)是一个核心概念。如果一个接口有且只有一个未实现的方法,那么这个接口就被称为函数式接口。在JDK 1.8中引入了一个新的注解@FunctionalInterface,将这个注解放在接口定义之前,就表示这个接口是一个函数式接口。编译器会检查该接口是否只有一个未实现的方法,如果定义了多个或者没有定义,编译器会报错。尽管这个注解不是必须的,但虚拟机仍然能够识别出函数式接口。这个注解的主要作用是防止误操作,加了这个注解之后接口将不允许出现多个未实现的方法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [jdk1.8之函数式接口](https://blog.csdn.net/liu_shi_jun/article/details/128562977)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [JDK1.8新特性--函数式接口(详解)](https://blog.csdn.net/Saintmm/article/details/109557103)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值