面向对象编程内功心法系列十八(聊一聊函数式编程)

171 阅读4分钟

1.引子

做java开发的小伙伴们,从jdk8起,都知道了并且喜欢写一些函数式编程风格的代码,那么今天,我们具体来聊一聊函数式编程这个话题。

在编程范式的世界里,有三种编程范式

  • 面向过程编程
  • 面向对象编程
  • 函数式编程

有人说,函数式编程真好!自从有了函数式编程,腰不疼,腿不酸,还不抽筋!比起面向对象编程风格的代码,简直一个简单优雅!

但是从内心底我们要认清一个事实,每一种编程范式,都是有它适合的场景,并不是说谁替代谁的问题。

我们可以从函数式编程的字面含义理解一下,在函数式编程中的 “函数” ,并不是我们定义一个类里面的函数(方法),它更倾向于说是数学函数,或者叫做表达式。即对于函数式编程来说,它是程序面向数学的更底层抽象,把计算过程描述为表达式,它更加适合于科学计算、统计分析、数据处理等场景。

当我们开发一些大型复杂业务系统的时候,还是面向对象编程会更加普适。当然代码设计实现的时候,写着面向对象编程风格的代码,顺带在合适的地方用上函数式编程,也还不错!做java开发的小伙伴,不就经常这么干吗?对吧

那么接下来,我就跟你聊一聊java中的函数式编程!

2.案例

2.1.示例代码

jdk在1.8版本中,引入了函数式编程。具体提供了三个新的语法概念

  • Stream流,通过操作符"."支持多个函数之间的级联操作
  • Lambda表达式,简化代码编写,让代码看起来简洁优雅
  • Functional Interface函数接口,将函数封装成接口,实现将函数当做参数使用

看一段示例代码

package cn.edu.anan.fun;
​
import java.util.Optional;
import java.util.stream.Stream;
​
/**
 * 函数式编程示例代码
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2022/1/2 11:43
 */
public class FunctionDemo {
​
    public static void main(String[] args) {
        Optional<Integer> min = Stream.of("cat", "dog", "pig", "monster", "tiger", "dragLong")
                .map(v -> v.length())
                .filter(l -> l <= 3)
                .min((o1, o2) -> o1 - o2);
        System.out.println("计算结果:" + min.orElse(0));
    }
}
​
// 执行结果
计算结果:3

上面的示例代码,即是java开发常规的函数式编程风格的代码,它的特点很显著

  • 通过操作符号 “.” 实现了链式操作
  • 方法参数传递的是表达式

下面我们分开来说Stream流、Lambda表达式、函数式接口。

2.2.Stream流

Stream流的作用,通过操作符号"."实现函数之间的级联调用,这样有什么好处呢?举一个简单示例。

假如我们需要实现一个表达式的求值:(a+b)*c-d,普通函数调用是这样的

subtract( multiply( add(a,b), c ), d )

函数之间嵌套调用,不利于代码的可读性,换种方式会不会更好呢?Stream流的方式

add(a,b).multiply(c).subtract(d)

通过Stream流的方式,代码清晰明了,可读性非常好!

这就是引入Stream流的价值,它实现了函数之间的级联调用,同时"."操作符号很好理解,对象.方法(参数) ,就是这个套路。

java中的Stream操作,分类为两种

  • 中间操作,比如示例代码中的map、filter
  • 终止操作,比如示例代码中的min

2.3.Lambda表达式

引入Lambda表达式的作用,简化代码编写。Lambda表达式由三块内容组成

  • 输入
  • 函数体
  • 输出
(a) ->{statement_1;statement_2...; return 输出}

a是输入;statement_1;statement_2...是函数体;return 是输出。

事实上,Lambda表达式是一个语法糖,底层是基于函数接口来实现的,我们把示例代码改造一下,你再看看

// Lambda表达式
Optional<Integer> min = Stream.of("cat", "dog", "pig", "monster", "tiger", "dragLong")
                .map(v -> v.length())
                .filter(l -> l <= 3)
                .min((o1, o2) -> o1 - o2);
​
// 函数接口(匿名内部类)
Optional<Integer> min2 = Stream.of("cat", "dog", "pig", "monster", "tiger", "dragLong")
                .map(new Function<String, Integer>() {
                    @Override
                    public Integer apply(String s) {
                        return s.length();
                    }
                })
                .filter(new Predicate<Integer>() {
                    @Override
                    public boolean test(Integer i) {
                        return i <= 3;
                    }
                })
                .min(new Comparator<Integer>() {
                    @Override
                    public int compare(Integer o1, Integer o2) {
                        return o1 - o2;
                    }
                });
​

如果没有Lambda表达式,通过匿名内部类方式实现函数参数传递,代码非常臃肿!这样看你应该能明白Lambda表达式的价值所在了。

2.4.函数式接口

上面示例代码中,涉及到Function、Predicate、Comparator都是jdk提供的函数式接口。它的作用我们在前面有说过,将函数封装成接口,实现将函数当做参数使用

那么你知道,函数式接口有什么要求吗?除了jdk提供好的函数式接口,我们能不能自己定义一些函数式接口呢?当然是可以的。

函数式接口的特点

  • 函数式接口,需要声明注解@FunctionalInterface
  • 函数式接口,只包含一个未实现的方法

比如Function接口

@FunctionalInterface
public interface Function<T, R> {
     /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}

比如Predicate接口

@FunctionalInterface
public interface Predicate<T> {
      /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}