本节内容:
-
为什么要进行函数式编程
-
什么是函数式编程
-
声明式编程以及引用透明性
-
编写函数式java的准则
-
迭代和递归
让我们假设你被要求对一个大型的遗留软件系统进行升级,而且这个系统你之前并不是非常了解。你是否应该接受维护这种软件系统的工作呢?
稍有理智的外包java程序员只会依赖下面这种言不由衷的格言做决定:“搜索一下代码中有没有使用 synchronized 关键字,如果有就直接 拒绝(由此我们可以了解修复并发导致的缺陷有多困难).否则进一步看看系统结构的复杂程度”
假设有这样一个系统,它不修改任何数据。维护这样的一个系统将是一个无以伦比的美梦, 因为你不再会收到任何由于某些对象在某些地方修改了某个数据结构而导致的意外报告。如果一 个方法既不修改它内嵌类的状态,也不修改其他对象的状态,使用 return 返回所有的计算结果, 那么我们称其为纯粹的或者无副作用的.
从另一个角度来看“无副作用”的话,我们就应该考虑不可变对象。不可变对象是这样一种 对象,它们一旦完成初始化就不会被任何方法修改状态。这意味着一旦一个不可变对象初始化完 毕,它永远不会进入到一个无法预期的状态。你可以放心地共享它,无需保留任何副本,并且由 于它们不会被修改,还是线程安全的。
“无副作用”这个想法的限制看起来很严苛,你甚至可能会质疑是否有真正的生产系统能够 以这种方式构建。我们希望结束本章的学习之后,你能够确信这一点。一个好消息是,如果构成 系统的各个组件都能遵守这一原则,该系统就能在完全无锁的情况下,使用多核的并发机制,因 为任何一个方法都不会对其他的方法造成干扰。此外,这还是一个让你了解你的程序中哪些部分 是相互独立的非常棒的机会。
这些思想都源于函数式编程,我们在下一节会进行介绍。但是在开始之前,让我们先看看函 数式编程的基石声明式编程吧。
一般通过编程实现一个系统有两种思考方式:
- 一种专注于如何实现,比如:“首先做这个, 紧接着更新那个,然后……”举个例子,如果你希望通过计算找出列表中最昂贵的事务,通常需 要执行一系列的命令:从列表中取出一个事务,将其与临时最昂贵事务进行比较;如果该事务开 销更大,就将临时最昂贵的事务设置为该事务;接着从列表中取出下一个事务,并重复上述操作。 这种“如何做”风格的编程非常适合经典的面向对象编程,有些时候我们也称之为“命令式” 编程,因为它的特点是它的指令和计算机底层的词汇非常相近,比如赋值、条件分支以及循环, 就像下面这段代码
Transaction mostExpensive = transactions.get(0);
if(mostExpensive == null)
throw new IllegalArgumentException("Empty list of transactions")
for(Transaction t: transactions.subList(1, transactions.size())){
if(t.getValue() > mostExpensive.getValue()){
mostExpensive = t;
}
}另一种方式则更加关注要做什么,比如:使用Stream API你可以指 定下面这样的查询:
Optional<Transaction> mostExpensive =
transactions.stream()
.max(comparing(Transaction::getValue));感兴趣的参考: <> 第13章