java8函数式编程笔记-破坏式更新和函数式更新

破坏式更新和函数式更新

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

破坏式更新:
  有一个方法,传入一个对象并返回结果。在方法结束之后传入的参数对象也被改变了,这就是破坏式更新。你不能保证调用这个方法之后后续是否还会使用传入的参数对象,因此破坏式更新在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.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值