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

小编

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

前言

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

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

面向过程的累计

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的接口,提供了遍历的功能。

面向对象的累计

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以及方法引用

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

// 这是传统的
sum = sumInJava5Oop(10, new SumImpl());
// 这是方法引用
sum = sumInJava5Oop(10, SumImpl::apply);

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

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

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

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

方法引用的格式

方法引用有以下四种类型

  • 引用静态方法 ContainingClass::staticMethodName 。

  • 引用某个对象的实例方法 containingObject::instanceMethodName

  • 引用某个类型的任意对象的实例方法 ContainingType::methodName

  • 引用构造方法 ClassName::new

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

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

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

sumInJava5Oop(10, (a,b) -> a+b);

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

函数式接口

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

函数式接口的定义:

  • 一个接口有且只有一个抽象方法。

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

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

  • 如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口。

  • 如果某个接口只有一个抽象方法,但我们并没有给该接口声明 @FunctionalInterface 注解,那么编译器依旧会将该接口看作是函数式接口。

  • 重写 Object 类里的方法不会导致函数式接口失效。

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

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

@FunctionalInterface
private 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 方法。

  • 如果子类继承父类并实现接口,实现类的优先级比接口高。

专注分享Java技术,跟我一起学习吧

长按识别二维码关注

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值