博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java8函数式编程笔记-破坏式更新和函数式更新
阅读量:6655 次
发布时间:2019-06-25

本文共 3401 字,大约阅读时间需要 11 分钟。

破坏式更新和函数式更新

什么是破坏式更新和函数式更新:

破坏式更新:

  有一个方法,传入一个对象并返回结果。在方法结束之后传入的参数对象也被改变了,这就是破坏式更新。你不能保证调用这个方法之后后续是否还会使用传入的参数对象,因此破坏式更新在java的函数式编程中是不被提倡的。这也是另一种副作用。
函数式更新:
  用函数式编程的方法解决问题,强调没有任何副作用
破坏式更新例子:

我们有一个类用来保存火车站点的票务信息(利用简单的单向链表),表示从A地到B地的火车旅行,旅途中我们需要换车,所以需要使用几个由onward字段串联在一起的TrainJourney对象,直达火车或者旅途的最后一段onward为null

class TrainJourney {    public int price;    public TrainJourney onward;    public TrainJourney (int price, TrainJourney t) {        this.price = price;        this.onward = t;    }}复制代码

假设我们有几个互相分隔的TrainJourney对象分别代表A到B,B到C的旅行。我们希望建立一段新的旅行,它能将两个TrainJourney对象串联起来(即从A到B到C) 首先我们采用的是传统命令式的方法:

public static TrainJourney link (TrainJourney a, TrainJourney b) {     if (a == null) {         return b;     }     TrainJourney t = a;     while (a.onward != null) {         t = a.onward;     }     t.onward = b;     return a; }复制代码

这个方法具体的执行应该不用多讲了,这里我们注意到的是t.onward = b;这个操作之后return的还是a,这就出现了一个问题,这里我们进行的操作是直接修改了参数a,也就是参数a在执行完这个方法之后原来的数据结构就被改变了。如果我们还用参数a,b传入这个方法,返回的数据和第一次便不一样了,这样就产生了副作用。这个缺陷我们需要克服。因此:

如果我们需要使用表示计算结果的数据结果,那么请创建它的一个副本而不要直接修改现存的数据结构。这个最佳的实践也适用于标准的面向对象程序设计。

public static TrainJourney link (TrainJourney a, TrainJourney b) {       if (a == null) {           return b;       }       TrainJourney t = new TrainJourney(a.price, a.onward);       TrainJourney t1 = a;       TrainJourney t2 = t;       while (t1.onward != null) {           t2.onward = new TrainJourney(t1.onward.price, t1.onward.onward);           t2 = t2.onward;           t1 = t1.onward;       }       t2.onward = b;       return t;}复制代码

上述代码就是我们修改之后的代码,但是我们可以看到while语句中多次使用了new关键字创建对象来复制链表。但是这种方法会导致过度的对象复制。这时候,如果我们采用函数式编程的方法:

public static TrainJourney append (TrainJourney a, TrainJourney b) {    return a == null ? b : new TrainJourney (a.price, append(a.onward, b));}复制代码

和上面一对比,函数式编程的优点显而易见

  • 代码量大大减少
  • 没有对象复制导致的开支,执行速度快

函数式编程的代码一大特点就是我们只需要编写操作的步骤(先做什么,后做什么),具体如何操作(先做什么的具体操作)不需要我们写。在上述的例子中,我们从代码能看到,我们先检查参数a是否为空,如果为空则返回b,如果不为空则返回一个新的TrainJourney对象,这个对象的票价是参数a的票价,onward为递归调用append函数返回的值,递归时的参数为参数a的onward和参数b,说起来很绕。我简单地理解为:

函数式编程的代码只保留流程,具体操作全部交给程序自行完成。是一个偷懒的过程

这段代码有一个特别的地方,它并未创建整个新 TrainJourney对象的副本——如果a是n个元素的序列,b是m个元素的序列,那么调用这个函 数后,它返回的是一个由n+m个元素组成的序列,这个序列的前n个元素是新创建的,而后m个元 素和TrainJourney对象b是共享的。

另一个例子:

先前我们使用的是链表的例子,现在我们试试其他数据格式,最常见的就是二叉树

class Tree {    public String key;    public int val;    public Tree left, right;        public Tree (String key, int newval, Tree l, Tree r) {        this.key = key;        this.val = newval;        this.left = l;        this.right = r;    }}复制代码

这时候,我们希望根据key更新二叉树的val,一般的写法如下:

public static Tree update (String key, int newval, Tree t) {    if (t == null) {       t = new Tree(key, newval, null, null);    } else if (key.equals(t.key)) {        t.val = newval;    } else if (key.compareTo(t.key) < 0) {        t.left = update (key, newval, t.left);    } else {        t.right = update (key, newval, t.right);    }}复制代码

但是这种方法都会对现有的树进行修改,这意味着使用树存放映射关系的所有用户都会感知到这些修改,即破坏了原来的数据结构。 那么函数式编程是怎么样的呢?

public static Tree append (String k, int newval, Tree t) {    return t == null ? new Tree (key, newval, null, null) :       k.equals(t.key) ?           new Tree (k, newval, t.left, t.right) :            k.compareTo(t.key) < 0 ?               new Tree (k, newval, append (k, newval, t.left), t.right) :                new Tree (k, newval, t.left, append (k, newval, t.right));}复制代码

这段代码中,我们只用一行语句进行条件判断,没有采用if-else-then是为了强调,该写法没有任何副作用。不过如果采用if-else-then语句也可以,在每一个条件判断之后都加上return.

转载地址:http://qsato.baihongyu.com/

你可能感兴趣的文章
10.19 iptables规则备份和恢复 firewalld的9个zone firewalld关
查看>>
自然语言处理NLP(三)
查看>>
苏州大学GCT
查看>>
go语言碎片整理之 time
查看>>
spring mvc 返回json 数据
查看>>
区块链教程Fabric1.0源代码分析Ledger blkstorage block文件存储
查看>>
JAVAScript强大的框架:Jquery(一)
查看>>
网络技术工作在哪里
查看>>
玩转Linux - shell 编程基础(二)
查看>>
我的友情链接
查看>>
马哥2016全新Linux+Python高端运维班第三周作业
查看>>
!.NET 4.0并行计算深入解读(FOR,FOREACH,Invoke)
查看>>
NFinal 揭秘之控制器
查看>>
apxs动态安装扩展模块
查看>>
java-第八章-幸运抽奖-实现注册功能
查看>>
连接池和数据源
查看>>
httpd服务的简单配置
查看>>
系统管理员权限的用户过多
查看>>
Netty高性能之道
查看>>
我的友情链接
查看>>