Java8精华-函数式编程-Consumer(二)

随着 Java 8 的发布,引入了多个函数式接口。我们可以在JDK源代码中找到它们,因为它们带有@FunctionalInterface注解。注解@FunctionalInterface有两个目的。

  1. 首先,它用作文档目的。它让开发人员知道它是一个函数式接口,并且可以为该接口实现 lambda 表达式。
  2. 其次,它通过提供编译时安全性来避免编程错误。例如,下面给出了编译时错误。
 

kotlin

代码解读

复制代码

@FunctionalInterface interface IFoo { // 编译时错误,因为没有抽象方法 }

除非遵循FunctionalInterface 准则,否则上述接口定义将无法编译,即它必须具有一个抽象方法。

这里需要注意的一件事是,任何没有使用 @FunctionalInterface 注解标记的接口,只要它只有一个抽象方法,仍然会被视为函数式接口。

例如,下面是一个函数式接口,并且可以为该接口实现 lambda 表达式。

 

arduino

代码解读

复制代码

interface IFoo { int foo(String s1); }

注解@FunctaionalInterface是可选的,强烈建议使用该注解来标记函数式接口。

现在,我们来看看Java 8中提供的内置函数式接口。

内置函数式接口

作为 Java 8 版本的一部分,引入了多个函数式接口。所有这些功能接口在使用 Java 集合处理数据时都有不同的用途。除此之外,旧的类(如 Runnable、Callable 和其他具有单个抽象方法的接口)也使用 @FunctionalInterface 注解。

下面是Java 8中引入的函数式接口

  1. Consumer
  2. Predicate
  3. Function
  4. Supplier
  5. BiFunction
  6. BiConsumer
  7. UnarayOperator
  8. BinaryOperator

所有这些接口都与 List、Set、Map 等集合一起使用。我们将看到如何实现这些接口。

Consumer

Consumer 接口是最常用的接口。对于集合对象上的 forEach() 的每次使用,都会有 Consumer 接口。例如,看看下面的代码。

整理了一份Java面试题。包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

 需要全套面试笔记的【点击此处】即可免费获取

ini

代码解读

复制代码

List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ints.forEach(i -> System.out.println(i));

换句话说,无论我们向 forEach() 方法提供什么 lambda 表达式作为参数,该 lambda 表达式都是 Consumer 接口的实现。

这是为什么?我们只需查看 forEach 方法接受的参数即可。下面是 Iterable 接口的 forEach() 方法定义。

 

javascript

代码解读

复制代码

default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); // 检查null for (T t : this) { action.accept(t); } }

可以看到 forEach() 方法是接受 Consumer 作为参数的默认方法。这个 forEach() 方法位于 Java 中每个集合接口都扩展的 Iterable 接口中

现在让我们看看 Consumer 接口的定义。

 

scss

代码解读

复制代码

@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接口有一个名为accept()的抽象方法。还有一个名为 andThen() 的默认方法,它本身返回 Consumer 对象。 andThen() 方法使我们能够通过将一个 Consumer 与另一个 Consumer 链接来实现强大的复合 lambda 表达式。

我们已经了解了 forEach 方法的 lambda 表达式:i -> System.out.println(i)。现在我们将了解如何得到这个 lambda 表达式。

第一步是编写一个匿名内部类,如下所示。(我知道前面已经讲过,因为要照顾到初学者,所以反复解释如何得出 lambda 表达式。当我们练习并进行一些实践时,我们可以直接编写该 lambda 表达式。)

这里是 forEach 方法的匿名内部类。

 

typescript

代码解读

复制代码

List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ints.forEach(new Consumer<Integer>() { @Override public void accept(Integer i) { System.out.println(i); } });

可以将上面的匿名内部类转换为 lambda 表达式,如下所示。

 

ini

代码解读

复制代码

List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ints.forEach((Integer i) -> System.out.println(i));

上一篇已经说过,我们甚至可以忽略数据类型,因为编译器会推断它,并且当只有一个参数时我们甚至可以使用括号。

 

ini

代码解读

复制代码

List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ints.forEach(i -> System.out.println(i));

如果我们现在想要打印集合中每个整数的平方,而不是简单地打印元素,该怎么办?

 

css

代码解读

复制代码

ints.forEach(i -> System.out.println(i * i));

立方呢?

 

css

代码解读

复制代码

ints.forEach(i -> System.out.println(i * i * i));

现在能看到 lambda 表达式的美妙之处以及使用匿名内部类编写这些表达式是多么痛苦?

现在我们知道Consumer 类有一个名为accept(T t) 的抽象方法,我们需要实现该方法;作为匿名内部类或作为 lambda 表达式,我们需要实现该方法。

在上面的所有示例中,我们直接将 lambda 表达式传给 forEach 方法。我们甚至可以将Lambda赋给一个变量,将这个变量传给forEach,如下所示。

 

ini

代码解读

复制代码

List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Consumer<Integer> printConsumer = i -> System.out.println(i); ints.forEach(printConsumer);

这就是函数式编程的意义。在一个示例中,我们直接将 lambda(函数表达式或简单函数)作为方法参数发送。在第二个示例中,我们将该函数分配给 Consumer 类型的变量。

当我们必须使用像 andThen() 这样的辅助函数来链接多个 Consumer 对象时,将函数分配给变量会派上用场。下一节将对此进行说明。

Consumer的 andThen()方法

Consumer 接口定义有一个默认方法 andThen()。这个方法使我们能够实现Consumer链。在某些情况下,我们可能需要对集合中的每个元素执行两个操作,一个接一个。例如,我们想要为每个元素依次计算平方和立方。此类场景可以使用 andThen() 方法非常轻松地实现。

 

ini

代码解读

复制代码

List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Consumer<Integer> c1 = i -> System.out.println(i * i); Consumer<Integer> c2 = i -> System.out.println(i * i * i); ints.forEach(c1.andThen(c2));

我们甚至可以将该 lambda 分配给另一个变量,并将该新变量发送给 forEach 方法,如下所示。

 

ini

代码解读

复制代码

Consumer<Integer> c1 = i -> System.out.println(i * i); Consumer<Integer> c2 = i -> System.out.println(i * i * i); Consumer<Integer> c3 = c1.andThen(c2); ints.forEach(c3);

为什么我们不能像下面这样在单个Consumer表达式中实现它?

 

css

代码解读

复制代码

ints.forEach(i -> { System.out.println(i * i); System.out.println(i * i * i); });

我们可以实现,它给出了完全相同的结果,并且看起来比 andThen 链接更简单。在这里有两个问题。

  1. 这陷入了命令式编程模式。 如果除了平方和立方之外我们还必须实现多个操作,那么这看起来非常必要且笨拙。
  2. 通过 andThen 链接,使代码是声明性的并且更具可读性。

通过命令式编程,我们为特定任务指定指令。通过声明式编程,我们声明操作而不是过于具体,框架会完成其余的工作。

命令式编程与声明式编程

为了从现实世界的角度来看这个问题,让我们以吃饭 的场景为例。我们去一家餐馆点一份炒面,但我们对它的制作方法太具体了。我们告诉厨师我们想要的——少点葱、辣椒放多点等等。

现在,如果餐厅老板设计了一份将这些选项组合在一起的炒面菜单,如下所示。

1、少葱

2、多辣

3、不加香菜

您只需走进餐厅,查看菜单并选择最好的选择,厨师就会为您制作。这是声明性的。我们指定我们想要这个。厨师已经有了它实现框架。我们不必告诉他所有的指令。

现在,在 Java 编程世界中,推动因素是什么?函数式接口和 lambda 表达式。

这就是 Consumer 接口及其相应的 lambda 实现的全部内容。在下一篇文章中,我们将了解 BiConusmer。

总结

注解@FunctionalInterface有两个目的。

  1. 首先,它用作文档目的,因为它让开发人员知道它是一个函数式接口,并且可以为该接口实现 lambda 表达式。
  2. 其次,它通过提供编译时安全性来避免编程错误。例如,下面给出了编译时错误。
  3. Consumer 接口具有名为accept() 的抽象方法。还有一个名为 andThen() 的默认方法,它本身返回 Consumer 对象。
  4. andThen() 方法使我们能够通过将一个 Consumer 与另一个链接来实现强大的复合 lambda 表达式。
  • 13
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值