用好JAVA中的函数式接口,轻松从通用代码框架中剥离掉业务定制逻辑 ...

打印 上一主题 下一主题

主题 556|帖子 556|积分 1668

大家好,又见面了。
今天我们一起聊一聊JAVA中的函数式接口。那我们首先要知道啥是函数式接口、它和JAVA中普通的接口有啥区别?其实函数式接口也是一个Interface类,是一种比较特殊的接口类,这个接口类有且仅有一个抽象方法(但是可以有其余的方法,比如default方法)。
当然,我们看源码的时候,会发现JDK中提供的函数式接口,都会携带一个 @FunctionalFunction注解,这个注释是用于标记此接口类是一个函数式接口,但是这个注解并非是实现函数式接口的必须项。说白了,加了这个注解,一方面可以方便代码的理解,告知这个代码是按照函数式接口来定义实现的,另一方面也是供编译器协助检查,如果此方法不符合函数式接口的要求,直接编译失败,方便程序员介入处理。

所以归纳下来,一个函数式接口应该具备如下特性:

  • 是一个JAVA interface类
  • 有且仅有1个公共抽象方法
  • 有@FunctionalFunction标注(可选)
比如我们在多线程场景中都很熟悉的Runnable接口,就是个典型的函数式接口,符合上面说的2个特性:
  1. @FunctionalInterface
  2. public interface Runnable {
  3.     /**
  4.      * When an object implementing interface Runnable is used
  5.      * to create a thread, starting the thread causes the object's
  6.      * run method to be called in that separately executing
  7.      * thread.
  8.      * <p>
  9.      * The general contract of the method run is that it may
  10.      * take any action whatsoever.
  11.      *
  12.      * @see     java.lang.Thread#run()
  13.      */
  14.     public abstract void run();
  15. }
复制代码
但是,我们在看JDK源码的时候,也会看到有些函数式接口里面有多个抽象方法。比如JDK中的 Comparator接口的定义如下:
  1. @FunctionalInterface
  2. public interface Comparator<T> {
  3.     int compare(T o1, T o2);
  4.     boolean equals(Object obj);
  5.     // 其他方法省略...
  6. }
复制代码
可以看到,Comparator接口里面提供了 compare和 equals两个抽象方法。这是啥原因呢?回答这个问题前,我们可以先来做个试验。
我们自己定义一个函数式接口,里面提供两个抽象方法测试一下,会发现IDEA中直接就提示编译失败了:

同样是这个自定义的函数式接口,我们修改下里面的抽象方法名称,改为 equals方法,会发现这样就不报错了:

在IDEA中可能更容易看出端倪来,在上面的图中,注意到12行代码前面那个 @符号了吗?我们换种写法,改为如下的方式,原因就更加清晰了:

原来,这个 equals方法,其实是继承自父类的方法,因为所有的类最终都是继承自Object类,所以 equals方法只能算是对父类接口的一个覆写,而不算是此接口类自己的抽象方法,所以此方法里面实际上还是只有 1个抽象方法,并没有违背函数式接口的约束条件。

函数式接口在JDK中的大放异彩

JDK源码 java.util.function包下面提供的一系列的预置的函数式接口定义:

部分使用场景比较多的函数式接口的功能描述归纳如下:
接口类功能描述Runnable直接执行一段处理函数,无任何输出参数,也没有任何输出结果。Supplier执行一段处理函数,无任务输入参数,返回一个T类型的结果。与Runnable的区别在于Supplier执行完之后有返回值。Consumer执行一段处理函数,支持传入一个T类型的参数,执行完没有任何返回值。BiConsumer与Consumer类型相似,区别点在于BiConsumer支持传入两个不同类型的参数,执行完成之后依旧没有任何返回值。Function执行一段处理函数,支持传入一个T类型的参数,执行完成之后,返回一个R类型的结果。与Consumer的区别点就在于Function执行完成之后有输出值。BiFunction与Function相似,区别点在于BiFunction可以传入两个不同类型的参数,执行之后可以返回一个结果。与BiConsumer也很类似,区别点在于BiFunction可以有返回值。UnaryOperator传入一个参数对象T,允许对此参数进行处理,处理完成后返回同样类型的结果对象T。继承Function接口实现,输入输出对象的类型相同。BinaryOperator允许传入2个相同类型的参数,可以对参数进行处理,最后返回一个仍是相同类型的结果T。继承BiFunction接口实现,两个输入参数以及最终输出结果的对象类型都相同。Predicate支持传入一个T类型的参数,执行一段处理函数,最后返回一个布尔类型的结果。BiPredicate支持传入2个相同类型T的参数,执行一段处理函数,最后返回一个布尔类型的结果。JDK中 java.util.function 包内预置了这么多的函数式接口,很多场景下其实都是给JDK中其它的类或者方法中使用的,最典型的就是Stream了——可以说有一大半预置的函数式接口类,都是为适配Stream相关能力而提供的。也正是基于函数式接口的配合使用,才是使得Stream的灵活性与扩展性尤其的突出。
下面我们一起来看几个Stream的方法实现源码,来感受下函数式接口使用的魅力。
比如,Stream中的 filter过滤操作,其实就是传入一个元素对象,然后经过一系列的处理与判断逻辑,最后需要给定一个boolean的结果,告知filter操作是应该保留还是丢弃此元素,所以filter方法传入的参数就是一个 Predicate函数式接口的具体实现(因为Predicate接口的特点就是传入一个T对象,输出一个boolean结果):
[code]/*** Returns a stream consisting of the elements of this stream that match* the given predicate.*/    Stream filter(Predicate

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

熊熊出没

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表