美文网首页
Java新特性之-函数式编程

Java新特性之-函数式编程

作者: jerry的技术与思维 | 来源:发表于2020-04-28 19:32 被阅读0次

函数式编程重要概念

在众多的编程范式中,大多数开发人员比较熟悉的是面向对象编程范式。一方面是由于面向对象编程语言比较流行,与之相关的资源比较丰富,比如Java,c++等。

另外一方面是由于大部分学校和培训机构的课程设置,都选择流行的面向对象编程语言。面向对象编程范式的优点在于其抽象方式与现实中的概念比较相近。比如,学生、课程、汽车和订单等这些现实中的概念,在抽象成相应的类之后,我们很容易就能理解类之间的关联关系。这些类中所包含的属性和方法可以很直观地设计出来。

函数式编程范式则相对较难理解。这主要是由于函数所代表的是抽象的计算,而不是具体的实体。因此比较难通过类比的方式来理解。
举例来说:

在一个学生信息管理系统中,可能会需要找到一个班级的某门课程的最高分数;在一个电子商务系统中,也可能会需要计算一个订单的总金额。看似风马牛不相及的两件事情,其实都包含了同样的计算在里面。

那么他们的共同点是什么呢?

对一个可迭代的对象进行遍历,同时在遍历的过程中执行自定义的操作。在计算最高分数的场景中,在遍历的同时需要保存当前已知最高分数,并在遍历过程中更新该值;在计算订单总金额的场景中,在遍历的同时需要保存当前已累积的金额,并在遍历过程中更新该值。

上面的话还是太抽象了,用直观的代码来表示:
计算学生的最高分数的代码

int maxMark = 0;
for (Student student : students) {
  if (student.getMark() > maxMark) {
    maxMark = student.getMark();
  }
}

计算订单的总金额的代码

BigDecimal total = BigDecimal.ZERO;
for (LineItem item : order.getLineItems()) {
   total = total.add(item.getPrice().multiply(new BigDecimal(item.getCount())));
}

你可能还会问题,这两段代码不一样啊,好了,用我们面向对象的抽象来提取下上面两段代码的共性:
该计算模式由 3 个部分组成:

  1. 保存计算结果的状态,有初始值。
  2. 遍历操作。
  3. 遍历时进行的计算,更新保存计算结果的状态值。

下面,我们把这 3 个元素提取出来,用代码表示

reduce(students, (mark, student) -> {
   return Math.max(student.getMark(), mark);
}, 0);
 
reduce(order.lineItems, (total, item) -> {
   return total.add(item.getPrice().multiply(new BigDecimal(item.getCount())))
}, BigDecimal.ZERO);

了解函数式编程的读者应该已经看出来了,这就是常用的 reduce 函数。

Java中的函数式编程

作为面向对象的编程语言,Java 中使用接口来表示函数。直到 Java 8,Java 才提供了内置标准 API 来表示函数,增加了java.util.function 包

Function<T, R>

Function<T, R>定义在java.util.function 包。

Function<T, R> 表示接受一个参数的函数,输入类型为 T,输出类型为 R。

Function 接口只包含一个抽象方法 R apply(T t),也就是在类型为 T 的输入 t 上应用该函数,得到类型为 R 的输出。

除了接受一个参数的 Function 之外,还有接受两个参数的接口 BiFunction<T, U, R>,T 和 U 分别是两个参数的类型,R 是输出类型。BiFunction 接口的抽象方法为 R apply(T t, U u)。超过 2 个参数的函数在 Java 标准库中并没有定义。

除了 Function 和 BiFunction 之外,Java 标准库还提供了几种特殊类型的函数:

  • Consumer<T>:接受一个输入,没有输出。抽象方法为 void accept(T t)。
  • Supplier<T>:没有输入,一个输出。抽象方法为 T get()。
  • Predicate<T>:接受一个输入,输出为 boolean 类型。抽象方法为 boolean test(T t)。
  • UnaryOperator<T>:接受一个输入,输出的类型与输入相同,相当于 Function<T, T>。
  • BinaryOperator<T>:接受两个类型相同的输入,输出的类型与输入相同,相当于 BiFunction<T,T,T>。
  • BiPredicate<T, U>:接受两个输入,输出为 boolean 类型。抽象方法为 boolean test(T t, U u)。

@FunctionalInterface

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
有意思的是,这个抽象方法的方法名无所谓,因为函数式接口可以被隐式转换为 lambda 表达式,lambda 是不需要方法名的。

Lambda 表达式和方法引用(实际上也可认为是Lambda表达式)上。
如定义了一个函数式接口如下:

@FunctionalInterface
interface GreetingService 
{
    void sayMessage(String message);
}

那么就可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):

GreetingService greetService1 = message -> System.out.println("Hello " + message);
函数式接口可以对现有的函数友好地支持 lambda。

内容太多,篇幅原因待续

相关文章

网友评论

      本文标题:Java新特性之-函数式编程

      本文链接:https://www.haomeiwen.com/subject/kribwhtx.html