前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从面向过程出发到函数式编程(上篇)

从面向过程出发到函数式编程(上篇)

作者头像
用户7386338
发布2020-05-29 11:03:46
5300
发布2020-05-29 11:03:46
举报
文章被收录于专栏:Java患者Java患者

小编

专注分享Java技术,坚持原创。你的关注和转发就是我们最好的动力。

前言

java8为我们引入的Lambda表达式、Stream ApI以及方法引用,它们为了java提供函数式编程的支持,虽然目前JDK已经出现到14的版本了,但是小编在工作中遇到一些员工中,对java的函数式编程并不有所了解,或者是不晓其原理。

今天我们就以一个对0~10间的累计为例子,从面向过程的编程方式开始,层层递进,演进到我们现在的函数式编程

面向过程的累计

代码语言:javascript
复制
private static int sumInJava5(int size) {    int sum = 0;    // range方法是自定义的,获取1到10的集合    List<Integer> values = range(1,size);    for (Integer i : values) {        sum += i;       }    return sum;}

这个例子使用到了jdk1.5的foreach语法,但其本身也是面向过程的,我们可以看出,加的操作是一步步的按照流程走,有先后之分。并不是通过调用对象的方法去进行累计。

说到这里,讲解一个疑惑,很多人只会用foreach语法,但是不知道集合和数组可以去使用foreach。其实是实现了Iterable的接口,提供了遍历的功能。

面向对象的累计

代码语言:javascript
复制
private static int sumInJava5Oop(int size, Sum sum) {    int result = 0;    // range方法是自定义的,获取1到10的集合    List<Integer> values = range(1,size);    for (Integer value : values) {        result = sum.apply(result, value);    }    return result;}
private interface Sum {    /**     * 加法运算     * @param a 左值     * @param b 右值     * @return 返回值     */    int apply(int a, int b);}
private static class SumImpl implements Sum{    @Override    public int apply(int a, int b) {        return a + b;    }}

这个累加是通过一个Sum类型的对象。调用它的apply方法实现的,是一个面向对象的过程。

在使用这个计算方法过程中,无论是通过传入实现类还是匿名内部类,代码是很繁琐的,jdk的开发者们为了解决这个问题,引入了Lambda表达式Stream ApI以及方法引用

使用传统的方式与方法引用调用我们的方法

代码语言:javascript
复制
// 这是传统的sum = sumInJava5Oop(10, new SumImpl());// 这是方法引用sum = sumInJava5Oop(10, SumImpl::apply);

我们的第二行代码就是我们方法引用形式,使用到的操作符“::”,这个操作符把方法引用分成两边,左边是类名或者某个对象的引用右边是方法名

值得注意的是:由于类名引用需要方法是静态的,因此,java8提供了接口可以有静态方法的特性,在此之前的版本是没有的。

此外使用方法引用有一个重要的特点,虽然它作为我们方法的参数,但是并不需要是相同的类型和相同的方法名,只要保证相同的签名就行(方法返回值和方法的参数),如下面几种方式:

代码语言:javascript
复制
// 可以是实例对象的引用sum = sumInJava5Oop(10, new SumImpl()::apply);// 可以是完全不同类型对象方法名字sum = sumInJava5Oop(10, OopDemo::calculate);

方法引用的格式

方法引用有以下四种类型

  • 引用静态方法 ContainingClass::staticMethodName 。
  • 引用某个对象的实例方法 containingObject::instanceMethodName
  • 引用某个类型的任意对象的实例方法 ContainingType::methodName
  • 引用构造方法 ClassName::new

这些都是java8提供新的编程语法支持的,它只要让编译器知道我们是方法参数、返回值以及在方法里面需要做什么就行。看到这里相信有不少人引发了另一个特性,没错就是我们的lambda表达式。

lambda表达式调用我们的累计方法

因为方法引用仅是一个方法实现的应用,在没有lambda表达式表达式的时候,我们选要在其他的地方编写我们的方法,而有了lambda表达式表达式以后,只需要在调用的位置写表达式就行:

代码语言:javascript
复制
sumInJava5Oop(10, (a,b) -> a+b);

注意:lambda表达式的主体仅包含一个表达式,且该表达式仅调用了一个已经存在的方法。方法引用的通用特性,方法引用所使用方法的入参和返回值与lambda表达式实现的函数式接口的入参和返回值一致。lambda可以看作是方法的实现。

函数式接口

我们可以发现,如果我们的Sum接口存在多个抽象方法,那么就不能用过lambda和方法引用进行传参操作。这是新语法提供高灵敏度的同时,也提供与之相应的保障。显然这种接口比较特殊,又有一个新的概念——函数式接口

函数式接口的定义:

  • 一个接口有且只有一个抽象方法。
  • 函数式接口的实例可以通过 lambda 表达式、方法引用或者构造方法引用来创建。

创建函数式接口注意事项:

  • 如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口。
  • 如果某个接口只有一个抽象方法,但我们并没有给该接口声明 @FunctionalInterface 注解,那么编译器依旧会将该接口看作是函数式接口。
  • 重写 Object 类里的方法不会导致函数式接口失效。

函数式接口可以有多个方法

虽然函数式接口的定义要求了我们一个接口有且只有一个抽象方法。但是java有提供接口的默认方法实现(default-method)来解决这个问题

代码语言:javascript
复制
@FunctionalInterfaceprivate interface Sum {  // 抽象方法  int apply(int a, int b);  // default-method  default int xx(int a, int b){      return a*b;  };  // Object的方法  String toString();}

‍‍尽管Sum接口有3个方法,但是方法引用和lambda表达式走的还是我们的抽象方法。

使用default-method注意事项

  • 类实现接口方法,即如果接口声明了 default 方法,并且某类实现了该接口,那么 default 方法将会被继承。
  • 如果有一个类继承了两个不同接口的同名 default 方法,jvm 编译器是无法识别到底该使用哪个方法的,必须重写 default 方法。
  • 如果子类继承父类并实现接口,实现类的优先级比接口高。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java患者 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档