Functional Programming In OOP
引言
函数式编程是一种编程泛型,将所有的运算都视为函数,只要输入相同的数据,就能得到稳定的输出。然而由于它的抽象程度高,离计算机硬件远,因此并没有被工业界广泛使用。然而在许多面向对象的编程语言中,能看到函数式编程的影子,它们往往可以帮助我们减少代码重复、理解目标和代码管理等。所以,学习一定的函数编程思想是非常有必要的。本文主要以Java为示例,讲解在OOP语言中函数式编程的应用。
什么是函数?
作为一个理/工科生,一说到函数,也许就会想到数学定义上的函数:一个集合到另一个集合的双射。更加直观的说,函数就是:我们只要输入一些数据就能得到一些数据。这样的数据转换是由函数内部实现所决定的。当然把函数当成一种算法的实现也没有问题。总之一千个人眼中有一千个哈姆雷特,只要选择你自己能够理解的方法,理解它就可以了。
那么在计算机领域,函数有如下的一些特点:
- 每个函数的输入是确定的(数据的类型、个数)。
- 每个函数的输出是确定的(数据的类型),并且只能输出一个数据。
- 函数的内部应当避免使用程序状态和可变对象
第一、二点很容易理解,函数体内部的实现依赖于输入和需求,而输出则依赖于函数的内部实现和输入,需求和函数内部实现是固定的,因此函数的输入和输出也应该是确定的。
函数可以没有输入或输出,但它会执行特定的功能。
对于第三点的理解,由于函数的内部实现的固定,因此不能把变化的(程序状态和可变对象)用在不变中。
什么是函数式编程?
在理解了函数是什么以后,我们就可以更进一步理解函数式编程了。
不同于OOP中万物皆对象的思想,函数式编程的思想更加的直接。将一系列的语句封装成多个不同的函数,从而解决一个复杂的问题;它聚焦于如何解决问题和表达式,而不是状态。
函数式方程还有以下特点:
- 递归:函数式编程采用递归处理while、if、for。
- 函数皆变量:所有的函数都可以当做变量使用。
- 不可变性:任何变量被创建以后都不能改变
函数式编程的优点:
- 易于debug
- Lazy evaluation:只在使用的时候运行函数。
- 支持并行编程:由于函数式编程使用不可变变量,因此可以轻松的实现并行编程,也不会有安全隐患。
- 易读:函数式编程的代码具有相当的易读性。
- 高效:函数式编程不依赖于外部资源或者变量,因此它们可以轻易的复用。
函数式编程的缺点:
- 术语复杂:函数编程的学习成本高。
- 递归:递归可能会造成资源浪费。
Java中函数式编程的应用
在Java中,函数式编程也有相当的应用场景。例如,对于一个排序算法:在不同的场景下可能要求升序或降序排列,为提高排序算法的可复用性,往往会需要一个东西,来控制排序的方式,也许可以是一个flag,若为1则升序,反之则降序。然而,通过这样的方式控制排序,过程中我们就必须重复书写一些代码,可以参考以下快速排序的实现。
/*** 快速排序* * @param arr 原数组* @param begin 起始元素* @param end 终止元素*/public static void quickSort(int[] arr, int begin, int end, int flag) {if (begin >= end) {return;}int pivot = begin;int i = begin;if(flag == 1){for (int j = begin + 1; j <= end; ++j) {if (arr[j] < arr[pivot]) {swap(arr, ++i, j);}}}else {for (int j = begin + 1; j <= end; ++j) {if (arr[j] > arr[pivot]) {swap(arr, ++i, j);}}}swap(arr, i, pivot);quickSort(arr, pivot + 1, end, flag);quickSort(arr, begin, pivot - 1, flag);}
可以看到:for循环中除了比较大小不同,其它代码都相同。那么有没有什么办法,可以让我们避免重复书写相同代码呢?相信在看完函数式编程的简介以后,你就想到把一个函数当做参数传入方法中。那么接下来我们就要介绍如何使用这样的方法。
Java作为一个纯粹的OOP语言,万物皆为对象,所以我们所需要的函数参数也应该封装为一个类,这个类就代表着一个函数。显然这样的类不需要任何字段和其它方法,它的内部只有一个方法(封装的函数),因此使用接口(interface)来表示这个类最为恰当。
再回到快速排序这个例子,我们针对不同的排序方法,要使用同一段代码实现,所以我们需要函数既能表示“大于”,也能表示“小于”。一个函数两个作用,这显然涉及到了方法的覆写(@Override),现在思路就很清晰了,我们需要传入的函数参数应该是封装了函数的接口的具体实现类,在这个类中覆写了接口中的函数。
这就是在Java中使用函数作为参数的实现思路,那么具体实现将在下一节探讨。