函数式编程与面向对象的设计方法在思路和手段上都各有千秋,在这里,我将简要介绍一下函数式编程与面向对象相比的一些特点和差异。

  1. 函数作为一等公民

在理解函数作为一等公民这句话时,让我们先来看一下一种非常常用的互联网语言JavaScript,相信大家对它都不会陌生。JavaScript并不是严格意义上的函数式编程,不过,它也不是属于严格的面向对象。但是,如果你愿意,你既可以把它当做面向对象语言,也可以把它当做函数式语言,因此,称之为多范式语言,可能更加合适。

如果你使用jQuery,你可能会经常使用如下的代码:

$("button").click(function(){
$("li").each(function(){
alert($(this).text())
});
});

注意这里each()函数的参数,这是一个匿名函数,在遍历所有的li节点时,会弹出li节点的文本内容。将函数作为参数传递给另外一个函数,这是函数式编程的特性之一。

再来考察另外一个案例:

function f1(){
    var n=1;
    function f2(){
      alert(n);
    }
    return f2;
  }
var result=f1();
result(); // 1

这也是一段JavaScript代码,在这段代码中,注意函数f1的返回值,它返回了函数f2。在倒数第2行,返回的f2函数并赋值给result,实际上,此时的result就是一个函数,并且指向f2。对result的调用,就会打印n的值。

函数可以作为另外一个函数的返回值,也是函数式编程的重要特点。

2.无副作用

函数的副作用指的是函数在调用过程中,除了给出了返回值外,还修改了函数外部的状态,比如,函数在调用过程中,修改了某一个全局状态。函数式编程认为,函数的副用作应该被尽量避免。可以想象,如果一个函数肆意修改全局或者外部状态,当系统出现问题时,我们可能很难判断究竟是哪个函数引起的问题。这对于程序的调试和跟踪是没有好处的。如果函数都是显式函数,那么函数的执行显然不会受到外部或者全局信息的影响,因此,对于调试和排错是有益的。

注意:显式函数指函数与外界交换数据的唯一渠道就是参数和返回值,显式函数不会去读取或者修改函数的外部状态。与之相对的是隐式函数,隐式函数除了参数和返回值外,还会读取外部信息,或者可能修改外部信息。

然而,完全的无副作用实际上做不到的。因为系统总是需要获取或者修改外部信息的。同时,模块之间的交互也极有可能是通过共享变量进行的。如果完全禁止副作用的出现,也是一件让人很不愉快的事情。因此,大部分函数式编程语言,如Clojure等,都允许副作用的存在。但是与面向对象相比,这种函数调用的副作用,在函数式编程里,需要进行有效的限制。

申明式的(Declarative)

函数式编程是申明式的编程方式。相对于命令式(imperative)而言,命令式的程序设计喜欢大量使用可变对象和指令。我们总是习惯于创建对象或者变量,并且修改它们的状态或者值,或者喜欢提供一系列指令,要求程序执行。这种编程习惯在申明式的函数式编程中有所变化。对于申明式的编程范式,你不在需要提供明确的指令操作,所有的细节指令将会更好的被程序库所封装,你要做的只是提出你要的要求,申明你的用意即可。

请看下面一段程序,这一段传统的命令式编程,为了打印数组中的值,我们需要进行一个循环,并且每次需要判断循环是否结束。在循环体内,我们要明确地给出需要执行的语句和参数。

 
public static void imperative(){
int[]iArr={1,3,4,5,6,9,8,7,4,2};
for(int i=0;i<iArr.length;i++){
System.out.println(iArr[i]);
}
}

与之对应的申明式代码如下:

public static void declarative(){
int[]iArr={1,3,4,5,6,9,8,7,4,2};
Arrays.stream(iArr).forEach(System.out::println);
}

可以看到,变量数组的循环体居然消失了!println()函数似乎在这里也没有指定任何参数,在此,我们只是简单的申明了我们的用意。有关循环以及判断循环是否结束等操作都被简单地封装在程序库中。

3.尾递归优化

递归是一种常用的编程技巧。使用递归通常可以简化程序编码,大幅减少代码行数。但是递归有一个很大的弊病——它总是使用栈空间。但是,程序的栈空间是非常有限的,与堆空间相比,可能相差几个数量级(栈空间大小通常只有几百K,而堆空间则通常达到几百M甚至上百G)。因此,大规模的递归操作有可能发生栈空间溢出错误,这也限制了递归函数的使用,并给系统带来了一定的风险。

而尾递归优化可以有效地避免这种状况。尾递归指递归操作处于函数的最后一步。在这种情况下,该函数的工作其实已经完成(剩余的工作就是再次调用它自己),此时,只需要简单得将中间结果传递给后继调用的递归函数即可。此时,编译器就可以进行一种优化,使当前的函数调用返回,或者用新函数的帧栈覆盖老函数的帧栈。总之,当递归处于函数操作的最后一步时,我们总是可以想方设法避免递归操作不断申请栈空间。

大部分函数式编程语言直接或者间接支持尾递归优化。

4.不变模式

如果读者熟悉多线程程序设计,那么一定对不变模式有所有了解。所谓不变,是指对象在创建后,就不再发生变化。比如,java.lang.String就是不变模式的典型。如果你在Java中创建了一个String实例,无论如何,你都不可能改变整个String的值。比如,当你使用String.replace()函数试图进行字符串替换时,实际上,原有的字符串对象并不会发生变化,函数本身会返回一个新的String对象,作为给定字符替换后的返回值。不变的对象在函数式编程中被大量使用。

请看以下代码:

static int[] arr={1,3,4,5,6,7,8,9,10};
Arrays.stream(arr).map((x)->x=x+1).forEach(System.out::println);
System.out.println();
Arrays.stream(arr).forEach(System.out::println);

代码第2行看似对每一个数组成员执行了加1的操作。但是在操作完成后,在最后一行,打印arr数组所有的成员值时,你还是会发现,数组成员并没有变化!在使用函数式编程时,这种状态是一种常态,几乎所有的对象都拒绝被修改。

5.易于并行

由于对象都处于不变的状态,因此函数式编程更加易于并行。实际上,你甚至完全不用担心线程安全的问题。我们之所以要关注线程安全,一个很大的原因是当多个线程对同一个对象进行写操作时,容易将这个对象“写坏”,更专业的说法是“使得对象状态不一致”。但是,由于不变模式的存在,对象自创建以来,就不可能发生改变,因此,在多线程环境下,也就没有必要进行任何同步操作。这样不仅有利于并行化,同时,在并行化后,由于没有同步和锁机制,其性能也会比较好。读者可以关注一下java.lang.String对象。很显然,String对象可以在多线程中很好的工作,但是,它的每一个方法都没有进行同步处理。

6.更少的代码

通常情况下,函数式编程更加简明扼要,Clojure语言(一种运行于JVM的函数式语言)的爱好者就宣称,使用Clojure可以将Java代码行数减少到原有的十分之一。一般说来,精简的代码更易于维护。而Java代码的冗余性也是出了名的,大部分对于Java语言的攻击都会直接针对Java繁琐,而且死板的语法(但我认为这也是Java的优点之一,正如本书第一段提到的“保守的设计思想是Java最大的优势”),然而,引入函数式编程范式后,这种情况发生了改变。我们可以让Java用更少的代码完成更多的工作。

请看下面这个例子,对于数组中每一个成员,首先判断是否是奇数,如果是奇数,则执行加1,并最终打印数组内所有成员。

数组定义:

  1. static int[] arr={1,3,4,5,6,7,8,9,10};
  2. 传统的处理方式:
  3. for(int i=0;i<arr.length;i++){
  4. if(arr[i]%2!=0){
  5. arr[i]++;
  6. }
  7. System.out.println(arr[i]);
  8. }

使用函数式方式:

Arrays.stream(arr).map(x->(x%2==0?x:x+1)).forEach(System.out::println);

可以看到,函数式范式更加紧凑而且简洁。

感兴趣的朋友可以看看这本电子书《Java8函数式编程入门

关于Java8函数式编程你需要了解的几点的更多相关文章

  1. Java8 函数式编程详解

    Java8 函数式编程详解 Author:Dorae Date:2017年11月1日23:03:26 转载请注明出处 说起Java8,可能很多人都已经知道其最大的改进,就是引入了Lambda表达式与S ...

  2. Java8函数式编程探秘

    引子 将行为作为数据传递 怎样在一行代码里同时计算一个列表的和.最大值.最小值.平均值.元素个数.奇偶分组.指数.排序呢? 答案是思维反转!将行为作为数据传递. 文艺青年的代码如下所示: public ...

  3. [2017.02.23] Java8 函数式编程

    以前学过Haskell,前几天又复习了其中的部分内容. 函数式编程与命令式编程有着不一样的地方,函数式编程中函数是第一等公民,通过使用少量的几个数据结构如list.map.set,以及在这些数据结构上 ...

  4. [一] java8 函数式编程入门 什么是函数式编程 函数接口概念 流和收集器基本概念

      本文是针对于java8引入函数式编程概念以及stream流相关的一些简单介绍 什么是函数式编程?   java程序员第一反应可能会理解成类的成员方法一类的东西 此处并不是这个含义,更接近是数学上的 ...

  5. 漫漫人生路,学点Jakarta基础-Java8函数式编程

    接口默认方法 Java8版本以后新增了接口的默认方法,不仅仅只能包含抽象方法,接口也可以包含若干个实例方法.在接口内定义实例方法(但是注意需要使用default关键字) 在此定义的方法并非抽象方法,而 ...

  6. Java8函数式编程的宏观总结

    1.java8优势通过将行为进行抽象,java8提供了批量处理数据的并行类库,使得代码可以在多核CPU上高效运行. 2.函数式编程的核心使用不可变值和函数,函数对一个值进行处理,映射成另一个值. 3. ...

  7. java8函数式编程实例

    什么是函数式编程 函数式编程是java8的一大特色,也就是将函数作为一个参数传递给指定方法.别人传的要么是基本数据类型,要么就是地址引用 ,我们要穿一个“动作”. Stream 说到函数式编程,就不得 ...

  8. Java8函数式编程以及Lambda表达式

    第一章 认识Java8以及函数式编程 尽管距离Java8发布已经过去7.8年的时间,但时至今日仍然有许多公司.项目停留在Java7甚至更早的版本.即使已经开始使用Java8的项目,大多数程序员也仍然采 ...

  9. 重识Java8函数式编程

    前言 最近真的是太忙忙忙忙忙了,很久没有更新文章了.最近工作中看到了几段关于函数式编程的代码,但是有点费解,于是就准备总结一下函数式编程.很多东西很简单,但是如果不总结,可能会被它的各种变体所困扰.接 ...

随机推荐

  1. javascript 核心语言笔记 6 - 对象

    对象是 JavaScript 的基本数据类型.是一种复合值:将很多值聚合在一起.对象可以看做是无序集合,每个属性都是一个名/值对.这种基本数据结构还有很多叫法,比如「散列」(hash).「散列表」(h ...

  2. Spring学习(三)

    1,Spring的事务管理机制 Spring事务管理高层抽象主要包括3个接口,Spring的事务主要是由他们共同完成的: l PlatformTransactionManager:事务管理器-主要用于 ...

  3. InvokeRequired 线程间访问

    zt: http://www.x2blog.cn/jinhong618/?tid=22389 问: f (this.InvokeRequired)            {               ...

  4. Python ToDo List

    这是我在学习python过程中,想做又没来得及做的事情一览.最初只有寥寥几个字,我会尽力去消化,让它不会只增不减. 由于博客园奇怪的算法,明明是一篇非常没有含量的东西(连字数都没有达到),居然能荣登p ...

  5. 进击的Python【第二章】:Python基础(二)

    Python基础(二) 本章内容 数据类型 数据运算 列表与元组的基本操作 字典的基本操作 字符编码与转码 模块初探 练习:购物车程序 一.数据类型 Python有五个标准的数据类型: Numbers ...

  6. 《UML大战需求分析》阅读随笔(二)

    在需求方面,我自己大体认为,分为两个部分:客户和软件公司. 客户:提出需求. 软件公司:解决需求. 这就是我所认为的 需求的关系. 就像书中所说的: 软件公司(项目组)始终都是跟着客户的后面追,客户需 ...

  7. 一个快速排序(分类)及使用类似思想实现选择问题[c++实现]

    一.快速排序(快速分类)算法: 问题描述:给定线性集中n个元素和一个整数k,1<=k<=n,要求找出这n个元素中第k小的元素. 思想:选取数组A中的某个元素 t=A[s],然后将其他元素重 ...

  8. 数组排序(java)

    代码如下: public class paixu{ public static void main(String []args){ int a[] = {78,23,56,34,12,45,67,89 ...

  9. jsoup获取文档类示例

    import java.io.IOException; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsou ...

  10. C# 中对 ArrayList 的排序

    ArrayList 元素 //目录条目类 public class FolderItem { public string filename; public string filetype; publi ...