让开发
成为一种享受!

【睡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接口,定义一些常用操作,减少代码的冗余。

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类型参数

运用的基础是先掌握,只有掌握才能熟练运用,干了这杯毒鸡汤。(小声BB:端午最后一天假了,T . T)

扫码关注公众号:Java开发乐园

在聊天窗口回复:vip

输入验证码,即可永久解锁本站全部文章

扫码关注【Java开发乐园】

Java开发乐园

扫码关注【东方】

微信:东方

验证码:
赞(0) 打赏
转载请注明出处:Java开发乐园 » 【睡JDK】Java函数式编程接口详解之Consumer、Function

来评论一下嘛~ 抢沙发

评论前必须登录!

 

我愿终生等候,换你刹那凝眸

留点🐾印

打赏即是一种肯定,谢谢您的肯定

支付宝扫一扫打赏

微信扫一扫打赏