Java函数式编程
  • 推荐1
  • 收藏9
  • 浏览1.1K

Java函数式编程

Pierre-Yves Saumont (作者)  高清华 (译者)

  • 书  号:978-7-121-33021-6
  • 出版日期:2017-12-07
  • 页  数:516
  • 开  本:16(185*235)
  • 出版状态:上市销售
  • 原书名: Functional Programming in Java: How to improve your Java programs using functional techniques
  • 原书号:9781617292736
  • 维护人:张春雨
纸质版 ¥119.00
《Java函数式编程》并不是一本关于Java的书,而是一本关于函数式编程的书。作者由浅入深地介绍了函数式编程的思维方式,并引导读者通过易于掌握的例子、练习和图表来学习和巩固函数式编程的基本原则和最佳实践。读者甚至可以在阅读的同时编写出自己的函数式类库!
本书非常适合对Java有所了解的程序员,无须任何基础的数学理论或是函数式编程经验即可快速上手!
0起点FP精髓直飞 可扩展性|可读性|可维护性编码能力直升
译者序

有幸受邀翻译本书。初见书名,心中不免有几分疑虑,难道又是一本教授怎么使用 Java 8 lambda来进行函数式编程的书吗?翻了几页,方觉自己大误。本书其实意在如何从零开始,逐步理清函数式编程的思维方式并编写基础类库,不仅授之以鱼,而且授之以渔。只不过由于 Java的受众实在太广,所以才使用这门语言罢了。
函数式编程有一个至关重要的前提,那就是函数的输出只能取决于函数的参数(我们会在书中看到生成随机数的例子)。初看上去似乎与 Java这门面向对象的语言不搭,但语言只是工具而已,正如你也可以在 Haskell中编写命令式风格的代码一样。在一个不太复杂甚至非并发的常规 Java系统中,由于程序内部状态的改变,多次调用同一个方法的返回值很可能是不一样的,更不用说所带来的副作用了。在函数式编程中,确定的输入决定了确定的输出,这意味着只要参数对了,结果一定在预期中。也就是说,函数式编程没有无法重现的 bug。在这样的前提下,单元测试相对容易实现,而且能极大地增强你的信心。(想想你对目前所在项目的单元测试有多大的信心?)许多个这样的函数复合起来,在不改变信心的同时能够提供更多、更强大的功能,进而带来更大的收益,如无状态的线程安全、必要时才计算的惰性求值、加快多次执行速度的记忆化等。
传统的命令式编程是计算机硬件的抽象,源自图灵机,其实就是外部输入、内部状态、对外部的输出以及对内部状态的改变。函数式编程源自 λ演算,即将变量和函数替换为值或表达式并根据运算符进行计算。函数式编程相比命令式编程,代码更简洁、可读性更强,这是因为它的思维方式更倾向于描述什么,而不是怎么做。所以学习过程反而更加自然,并且不需要多么高深的数学基础。可是我们也知道,软件开发没有银弹。新的方法论也会带来新的问题,需要运用新知识来解决。幸运的是,新知识的坑已经有人帮你踩过了,高阶函数、偏应用函数、复合函数、柯里化、闭包……软件开发从来不缺术语。幸好它们并非高不可攀,作者在第 2章中帮你扫清疑虑,并在后续章节中挑战惰性求值、记忆化、状态处理、应用作用还有 actor等更高级的技术。你说 Monad?作者才不告诉你它究竟是什么,但是看完本书你自然就领悟了。
函数式编程不是万能药。它有自己擅长的领域,也有自己的弱项。函数式编程是级别更高的抽象。高级别抽象带来的收益就是易读、好写,可是有些低级别的事情(如果你真的需要的话)可能就不容易完成。函数式编程没有副作用,导致无法完成输入 /输出操作。尽管如此,你也会在本书中看到一些解决办法。函数式编程没有变量,因此无法改变循环的终止条件,故而没有循环,严重依赖于用递归来抽象循环。在某些情况下可能会影响性能,所以你还会在本书中看到一些性能与情怀之间的权衡。绝大部分的编程最佳实践都是针对某个特定场景而言的,因此脱离业务场景直接讨论技术并不可取。拥有函数式编程的思维,你就拥有了解决问题的另一种选择,但是条条大路通罗马,千万别钻牛角尖。程序是对现实世界的建模,“不要让世界适应你的模型,要让你的模型适应世界。”
高清华

译者简介

高清华
亚马逊软件研发工程师。工作十多年来,在简洁代码、自动化测试、持续交付、 DevOps等方面都有着丰富的经验。他是《 DevOps实践》译者之一。其技术博客的网址是 http://qinghua.github.io/,希望能以通俗易懂的语言普及 IT技术。

为什么要函数式编程

函数式编程中没有赋值语句,因此变量一旦有了值,就不会再改变了。更通俗地说,函数式编程完全没有副作用。除了计算结果,调用函数没有其他作用。这样便消除了 bug的一个主要来源,也使得执行顺序变得无关紧要 —因为没有能够改变表达式值的副作用,可以在任何时候对它求值。这样便把程序员从处理控制流程的负担中解脱出来。由于能够在任何时候对表达式求值,所以可以用变量的值来自由替换表达式,反之亦然 —程序是“引用透明”的。这样的自由度让函数式的程序比它们的一般对手在数学上更易驾驭。
—John Hughes
Why Functional Programming Matters
我称之为十亿美元的错误……我当时的目标是确保所有引用的使用都应该绝对安全,由编译器自动执行检查。但是我无法抵制引入一个空引用的诱惑,仅仅是因为它很容易实现。这导致了无数的错误、漏洞和系统崩溃,它很可能在过去的四十年中造成了十亿美元的痛苦和损害。
—Tony Hoare
测试程序可以是一个证明 bug存在的有效方式,但是对于证明 bug不存在还是无可奈何。
—Edsger W. Dijkstra
测试本身并不能提高软件质量。测试结果是质量的指标,但它们并不能提高质量。尝试通过增加测试来提高软件质量就像是尝试通过经常称自己的体重来减肥一样。
—Steve McConnell
注释的合理运用是为了补偿我们无法成功地在代码中表达自己。
—Robert C. Martin
编程的难点并不是解决问题,而是决定要解决什么问题。
—Paul Graham
面向对象编程通过封装变化的部分使代码容易理解。函数式编程通过最小化变化的部分使代码容易理解。
—Michael Feathers
我总是发现计划没有用,但是制订计划不可或缺。
—Dwight D. Eisenhower
设计软件有两种方法:一种是简单化,使其明显没有缺陷;另一种是复杂化,使其没有明显缺陷。前者要难得多。
—Tony Hoare
如果我们询问客户他们想要什么,他们只会说:“更快的马。”
—Henry Ford
尽管一些声明式程序员对等式推理( equational reasoning)只会耍嘴皮子,但函数式语言的用户每次运行编译器时都会用到它们,无论他们是否注意到。
—Philip Wadler
How to declare an imperative
我们并不试图争取 Lisp程序员;我们在追赶 C++程序员。我们设法在他们到达 Lisp的半路上拽走他们。 —Guy Steele
人们“得到”类型,一直用着它们。告诉某人他不能用香蕉来敲钉子并不令其感到惊讶。
—Unknown
TDD代替了类型检查器……正如烈酒代替了悲伤。 —byorgey
首先,调试的难度是编写代码的两倍。因此,即使你把代码写得精彩绝伦,根据定义,你还不足以聪明地调试代码。 —Brian W. Kernighan和 P. J. Plauger
一旦开始编程,令我们感到惊讶的是,很难让程序按预想的方案正确地工作。不得不靠调试来弄明白。我还记得当意识到我此生的大部分时间都会用于寻找自己程序中的错误时的那一刻。
—Maurice Wilkes (1949)

序言

编程序既有趣又多金。许多人为了乐趣而编程,还能赚钱。从这种意义上说,程序员有点像演员、音乐家或职业足球运动员。似乎是一个梦想,直到作为程序员的你开始负起真正的责任。从这个角度上说,编写游戏或办公应用程序都没有什么大不了的。如果应用程序有一个 bug,你只修复它并发布一个新版本即可。但如果你编写的程序被人们依赖,并且还不能简单地发布一个新版本后让用户们自行安装,那就是另一种情况了。当然, Java并非用于编写监控核电站或控制飞机的应用程序,或者是一个简单的 bug就可能会置人类生命于风险中的系统。但如果你的程序用于管理互联网骨干,你一定不会愿意在奥运会开幕前一天发现一个讨厌的 bug,导致整个国家的电视传输失败。对于这样的程序,你希望确保它可以被证明是正确的。
大多数命令式程序无法被证明是正确的。测试只允许我们在测试失败时证明程序不正确。成功的测试说明不了什么问题。你无法证明发布的程序是不正确的。就单线程程序而言,大量的测试也许能够说明你的代码大部分是正确的。但是对于多线程应用程序,条件组合的数量使测试成为不可能。显然,我们需要另一种不同的方式来编写程序。在理想情况下,这种方法将允许我们证明程序是正确的。因为这一般不是完全可能的,所以一个很好的折中是明确分离程序中可以被证明为正确的部分和不能证明为正确的部分。这就是函数式编程技术所能提供的。
函数式编程的定义与函数式程序员一样多。有人说函数式编程是用函数编程。这是对的,但它无助于你了解这种编程范式的优势。更重要的是,函数式编程需要将抽象推至极致的理念。它允许明确分离程序中可以被证明为正确的部分与输出取决于外部条件的其他部分。通过这种方式,函数式程序是不太容易产生 bug的程序,它的 bug只会驻留在特定的受限区域。
可以采用许多种技术来实现这一目标。使用不可变数据虽然不仅限于函数式编程,但却是这样一种技术。如果数据不能更改,你将不会有任何(不良的)意外,数据不会过期或损坏,没有竞争条件,无须锁住并发访问,也不会有死锁的风险。可以毫无风险地共享不可变数据。你不需要生成防御性副本,那就没有了忘记这样做的风险。另一种技术是抽象控制结构,因此你不必冒着混淆循环索引和退出条件的风险一次又一次地编写相同的结构。完全删除使用 null引用(无论是隐式还是显式)将把你从臭名昭著的 NPE(空指针异常, NullPointerException)中解放出来。通过所有这些技术(还有更多),你可以确信:如果程序通过编译,那它就是正确的(也就是说,它的实现没有 bug)。虽然这样做并不能消除所有可能的 bug,但它更加安全。
计算机从一开始就基于寄存器中的可变值使用了命令式范式。正如其他许多被称为“命令式语言”的编程语言一样, Java似乎在很大程度上依赖于这种范式,但根本没有必要。如果你是有经验的 Java程序员,你可能会惊讶地发现无须更改变量的值就可以编写有用的程序。这并非函数式编程的必要条件,但是函数式程序员几乎总是自由自在地用着不可变数据。你可能也难以相信,可以在不使用 if…else结构、 while还有 for循环的情况下编写程序。再说一次,避免这样的结构并不是使用函数式范式的一个条件,但是如果你愿意,就可以避免这种结构,从而得到更安全的程序。所以即使 Java通常被视为“命令式语言”,但它其实不是。没有命令式语言,也没有函数式语言。相信它们的存在就像是认为英语更适用于商业文档,而意大利语更适用于歌剧,法语更适用于情诗,德语更适用于哲学(或你能想象到的任意组合)。差异可能存在,但它们大多是文化方面的,编程语言也是如此。 Java是一门命令式语言,这是因为大多数 Java程序员是命令式程序员,而 Java的文化也是命令式的。与此相反, Haskell程序通常以函数式风格编写,因为程序员们都想要选择这门语言来进行函数式编程。但是可以在 Haskell中编写命令式程序,也可以用 Java编写函数式程序。不同之处在于,Haskell比 Java更加“对函数式友好”。
所以问题是:“你应该用 Java来进行函数式编程吗?”令人惊讶的是(在给定本书主题的情况下),答案为否。要是可以自由地选择任何语言,我会建议你不应该为这个目的而选择 Java。但是你通常没有这样的自由。在撰写关于使用 Java进行函数式编程的文章时,我收到的大多数负面评论都是“你不应该为此使用 Java。这样用不是 Java的本意。”或者“为什么要用 Java,用 Haskell、Scala或其他什么不是更好?”
在现实中,你通常无法选择语言。如果你在一个公司工作,可能必须使用公司的语言,或者至少是你所在的团队为你正在工作的项目选择的语言。从这个角度上看,Haskell从来都不是一个可选项。通常,除了 Java,你别无选择。如果你处于选择语言的位置,除了使用自己熟悉的语言或是使用允许重用一些旧代码或适合环境或是其他一些条件的语言之外,可能也别无选择。本书面向的正是你,除了使用 Java之外别无选择的 Java程序员,尽管你希望能受益于函数式编程的安全性。
在 Java中使用函数式编程技术往往会导致你反对所谓的“最佳实践”。实际上,这些实践中有许多都没有用,有些还确实是非常糟糕的做法。从不捕获错误就是其中之一。作为 Java程序员,你可能已经了解到不应该捕获 OOME(内存不足的错误, Out Of Memory Error)或是你无法处理的其他类型错误。也许你甚至知道不应该捕获 NPE(空指针异常, NullPointerExceptions),因为它们表示有 bug,你应该让应用程序崩溃并修复它。不幸的是, OOME和 NPE都不会使应用程序崩溃。它们只会使所在的线程崩溃,并留下处于某种不确定状态的应用程序。即使它们发生在主线程中,如果某些非后台线程正在运行,它们也可能无法使应用程序崩溃。当所有的应用程序都是单线程时,这种“最佳实践”是正确的。而现在它是一个非常糟糕的实践。你应该捕获所有的异常,尽管可能不在一个 try…catch块中。函数式编程的真言是“始终捕获,绝不抛出”。
在我们的函数式编程之旅中还将挑战其他许多最佳实践。其中一个是:“不要重复发明轮子”,尽管它与 Java或命令式编程没有直接关系。想想看。某次,有人发明了轮子。那时候,大概是用刚性材料制造并随轴转动的圆。从那时起,轮子已经被重复发明许多次了。如果没有,那就不会有汽车,不会有火车,几乎没有什么东西会使用轮子。所以你应该继续努力一次次地重复发明轮子。不仅在将来会给我们带来更好的轮子,而且它具有挑战性,有回报,还有乐趣。(如果你认为当今的汽车轮子是圆形的,那么最好再思考一下,没有车能够开在圆形的轮子上!)

致谢
我要感谢许多参与制作本书的人。
首先,非常感谢我的策划编辑 Marina Michaels。非常荣幸与你一起工作,此外,还有你在手稿上的出色工作。
也非常感谢我的技术编辑 Mark Elston和我的技术审校 Alessandro Campeis,你们帮我把这本书制作得比我自己弄要好得多。
还要谢谢所有的评论者、 MEAP的读者以及其他所有提供反馈和评论的人!如果没有你们的帮助,这本书不会是今天这个样子。特别的,我要感谢所有花时间审校和评论这本书的人: Aditya Kumar、Al Krinker、Andy Kirsch、Andy Knight、 Anthony Moralez、Arun Allamsetty、Barry Kern、Boris Vasile、Bruce Hernandez、 Charles Feduke、Chris Kirk、David Drummond、Davide Fiorentino lo Regio、Erwin van Eijk、Gualtiero Testa、Ivan Milosavljevic’、Jan Vorwerk、Jér.me Baton、Joshua McAdams、Julian Templeman、Maria Gemini、Norbert Kuchenmeister、Philippe Charrière、Piotr Bzdyl、Rambabu Posa、Sebastian H.hnel、Sebastian Metzger、 Simeon Leyzerzon、Tarin Gamberini、Ursin Stauss、William Wheeler、Zach Schwartz和 Zorodzayi Mukuya。

关于本书

这不是一本关于 Java的书。这本书是关于函数式编程的,它是讲解编写软件程序的不同方法的。“不同”意味着它与称为“命令式范式”的编写软件的“传统”方式不同。本书涉及应用函数式范式进行 Java编程。
没有像“函数式语言”这样的东西,只有或多或少对函数式友好的语言。虽然我在本书中使用 Java,但你可以将我所教的所有原则应用于任何其他语言。只是实现这些原则的方式有所不同。你可以用任何语言编写函数式程序,甚至是那些据说根本不是函数式的语言;同样,你也可以使用对函数式最友好的语言来编写命令式程序。
随着 Java 8的发布,一些函数式特性已被添加到了 Java语言中。但是正如本书不是关于 Java的,它也不是关于这些具体的 Java 8特性的。在这本书中,我大量地使用了某些特性,并几乎忽略了其他特性。如果你的目标是学习如何使用 Java 8的函数式特性,本书并不合适。Urma、Fusco和 Mycroft编写的 Java 8 in Action(Manning, 2014)会是一个更好的选择。
另一方面,如果你想了解什么是函数式编程,如何构建函数式数据结构,以及函数式编程范式如何帮助你编写更好的程序(有时使用 Java 8的特性,有时又避免使用它们),那么这就是为你量身定做的书。
本书面向的读者
本书面向具有一些 Java编程经验的读者,需要对 Java泛型有较好的理解。如果你发现自己不了解 Java的构造(例如实现为方法的泛型常量或者是参数化方法调用),请不必担心:我将解释它们的含义以及为什么需要它们。
你不必具备函数式编程的经验,或是了解基础的数学理论。第 2章将会让你回忆起什么是一个函数。不会再用其他的数学知识了。
我介绍的所有函数式技术都与它们相应的命令式相关,所以我希望你具有在 Java中使用命令式编程的经验。

如何使用本书
希望读者能顺序阅读本书,因为每一章都建立在前文概念的基础之上。第 14章和第 15章是仅有的例外,不会使用你在第 12章和第 13章中学习的内容。也就是说,如果你愿意,可以跳过第 12章和第 13章;它们提供了更先进的技术,理解了会很有用,但是你可能不会在自己的程序中使用。
我用了“阅读”这个词,但这本书并不只是为了阅读而生,它只有极少部分的理论。为了充分利用这本书,请在电脑前阅读,随时动手做练习。每一章都包含了一些练习,并提供了必要的说明和提示,以帮助你找到答案。所有的代码都可以从 GitHub(http://github.com/fpinjava/fpinjava)和出版商的网站 https://www.manning. com/books/functional-programming-in-java上免费下载。每个练习都附带一个推荐答案和 JUnit测试,你可以用它来验证你的答案是否正确。
为了导入 IntelliJ(推荐)、NetBeans或 Eclipse中,代码附带了所有必需的要素,虽然在撰写本文时, Eclipse(Mars 4.5.1)尚未完全兼容 Java 8。可以通过源代码或者使用 Gradle导入项目。Gradle的任何版本都可以,因为它能够自动下载正确的版本。
请注意,我并不希望你仅靠阅读文章就能够理解本书中出现的大部分概念。做练习可能是学习过程中最重要的部分,所以我鼓励你不要跳过任何练习。有些练习可能做起来很困难,而你可能会忍不住想看看推荐答案。这样做完全没问题,但是你应该在那之后回到练习中,不看答案再做一次。如果只是翻答案,你可能会在之后尝试解决更高级的练习时再次遇到问题。
这种方式不需要烦琐地打字,因为几乎没有什么要复制的。大部分练习由编写方法的实现组成,我为此提供了环境和方法签名。没有多于十几行代码的练习;大多数都是四五行。
一旦你完成了练习(即你的实现通过编译),只需运行相应的测试来验证它是否正确即可。
值得注意的是,每一章中的各个练习都是独立的,所以在同一章中,上一个练习创建的代码会复制到下一个练习中。这种做法的必要性在于,因为每个练习通常会建立在前面的练习基础之上,因此虽然可以使用相同的类,但是实现不同。所以,不要在完成前面的练习之前先看后面的练习,因为你会看到还未做的练习的答案。
你可以下载代码的存档文件,也可以用 Git clone。我强烈建议用 clone,因为代码可能会更改,通过简单的 pull命令来更新代码比重新下载完整的存档文件效率更高。
练习的代码都被分到了各个模块中,它们的名字与章节标题而不是章节号大体上相对应。因此, IDE将按字母顺序排列它们,而不会按照它们在书中出现的顺序排序。为了帮助你了解每个章节对应的模块,我还在随书附带代码( http://github. com/fpinjava/fpinjava)的 README文件中提供了一个包含相应模块名称的章节列表。

设定期望
函数式编程并不会比命令式编程更难,只是不同而已。你可以用这两个范式来解决相同的问题,但是从一个转换到另一个时可能效率较低。学习函数式编程就像是学习一门外语。正如你想着一门语言并翻译到另一门语言不可能效率较高那样,你也不能想着命令式并把代码翻译为函数式。并且正如你需要学会用新的语言来思考那样,你也需要学会函数式地思考。仅靠阅读不足以学习函数式地思考,编写代码必不可少。所以你必须多加练习。
这就是为什么我不希望你只是通过阅读来了解这本书,为什么我要提供这么多练习?你需要做练习,以完全掌握函数式编程的概念。并不是因为这个主题很复杂,不可能通过阅读来理解它,而是因为如果你只通过阅读(不做练习)就能理解它,那你很可能不需要这本书。
出于所有的这些原因,练习是充分利用本书的关键。我鼓励你在继续阅读之前尝试解决每个练习。如果你找不到答案,请再试一试,而不是直接翻阅我提供的答案。如果你理解一些东西很困难,请在论坛上提问(见下一小节)。在论坛上提出问题和获得答案不仅可以帮助你,还可以帮助回答问题的人(以及其他有同样问题的人)。我们都通过回答问题(顺便说一下,主要是我们自己的问题)来学习,而较少通过问问题的方式。

目录

目录

第1章 什么是函数式编程............................. 1

1.1 函数式编程是什么 .............................................2

1.2 编写没有副作用的程序 .....................................4

1.3 引用透明如何让程序更安全 .............................6

1.4 函数式编程的优势 .............................................7

1.5 用代换模型来推断程序 .....................................8

1.6 将函数式原则应用于一个简单的例子 .............9

1.7 抽象到极致 .15

1.8 总结 .............16

第2章 在Java中使用函数 ............................17

2.1 什么是函数 .18

2.1.1 现实世界里的函数 ...............................18

2.2 Java中的函数 ....................................................24

2.2.1 函数式的方法 .......................................24

2.2.2 Java的函数式接口与匿名类 ...............30

2.2.3 复合函数 ...............................................31

2.2.4 多态函数 ...............................................32

2.2.5 通过 lambda简化代码 ..........................33

2.3 高级函数特性 ...................................................36

2.3.1 多参函数怎么样 ...................................36

2.3.2 应用柯里化函数 ...................................37

2.3.3 高阶函数 ...............................................38

2.3.4 多态高阶函数 .......................................39

2.3.5 使用匿名函数 .......................................43

2.3.6 局部函数 ...............................................45

2.3.7 闭包 .46

2.3.8 部分函数应用和自动柯里化 ...............48

2.3.9 交换部分应用函数的参数 ...................53

2.3.10 递归函数 .............................................54

2.3.11 恒等函数 ..............................................56

2.4 Java 8的函数式接口 .........................................58

2.5 调试lambda ..59

2.6 总结 .............62

第3章 让Java更加函数式 ............................63

3.1 使标准控制结构具有函数式风格 ...................64

3.2 抽象控制结构 ...................................................65

3.2.1 清理代码 ...............................................69

3.2.2 if … else的另一种方式 ........................73

3.3 抽象迭代 .....78

3.3.1 使用映射抽象列表操作 .......................79

3.3.2 创建列表 ...............................................80

3.3.3 使用 head和 tail操作...........................81

3.3.4 函数式地添加列表元素 .......................83

3.3.5 化简和折叠列表 ...................................83

3.3.6 复合映射和映射复合 ...........................90

3.3.7 对列表应用作用 ...................................91

3.3.8 处理函数式的输出 ...............................92

3.3.9 构建反递归列表 ...................................93

3.4 使用正确的类型 ...............................................97

3.4.1 标准类型的问题 ...................................97

3.4.2 定义值类型 ...........................................99

3.4.3 值类型的未来 .....................................103

3.5 总结 ...........103

第4章 递归、反递归和记忆化 ................... 104

4.1 理解反递归和递归 .........................................105

4.1.1 探讨反递归和递归的加法例子 .........105

4.1.2 在 Java中实现递归 ............................106

4.1.3 使用尾调用消除 .................................107

4.1.4 使用尾递归方法和函数 .....................107

4.1.5 抽象递归 .............................................108

4.1.6 为基于栈的递归方法使用一个直接替代品 ...........................................112

4.2 使用递归函数 .................................................115

4.2.1 使用局部定义的函数 .........................115

4.2.2 使函数成为尾递归 .............................116

4.2.3 双递归函数:斐波那契数列示例 .....117

4.2.4 让列表的方法变成栈安全的递归 .....120

4.3 复合大量函数 .................................................123

4.4 使用记忆化 .....................................................127

4.4.1 命令式编程中的记忆化 .....................127

4.4.2 递归函数的记忆化 .............................128

4.4.3 自动记忆化 .........................................130

4.5 总结 ...........136

第5章 用列表处理数据 ............................. 138

5.1 如何对数据集合进行分类 .............................138

5.1.1 不同的列表类型 .................................139

5.1.2 对列表性能的相对期望 .....................140

5.1.3 时间与空间,时间与复杂度的取舍 .141

5.1.4 直接修改 .............................................142

5.1.5 持久化数据结构 .................................143

5.2 一个不可变、持久化的单链表实现 .............144

5.3 在列表操作中共享数据 .................................148

5.3.1 更多列表操作 .....................................150

5.4 使用高阶函数递归折叠列表 .........................155

5.4.1 基于堆的 foldRight递归版 ................162

5.4.2 映射和过滤列表 .................................164

5.5 总结 ...........167

第6章 处理可选数据 ................................ 168

6.1 空指针的问题 .................................................169

6.2 空引用的替代方案 .........................................171

6.3 Option数据类型 ..............................................174

6.3.1 从 Option中取值 ................................176

6.3.2 将函数应用于可选值 .........................178

6.3.3 复合 Option处理 ................................179

6.3.4 Option的用例 .....................................181

6.3.5 复合 Option的其他方法 ....................186

6.3.6 复合 Option和 List .............................189

6.4 Option的其他实用程序 ..................................191

6.4.1 检查是 Some还是 None .....................191

6.4.2 equals和 hashcode ..............................192

6.5 如何及何时使用Option ..................................193

6.6 总结 ...........195

第7章 处理错误和异常 ............................. 197

7.1 待解决的问题 .................................................197

7.2 Either类型 ..199

7.2.1 复合 Either ...........................................200

7.3 Result类型 .203

7.3.1 为 Result类添加方法 .........................204

7.4 Result模式 .206

7.5 Result处理进阶 ...............................................213

7.5.1 应用断言 .............................................214

7.5.2 映射 Failure .........................................215

7.5.3 增加工厂方法 .....................................218

7.5.4 应用作用 .............................................220

7.5.5 Result复合进阶 ..................................222

7.6 总结 ...........226

第8章 列表处理进阶 ................................ 228

8.1 length的问题 ...................................................229

8.1.1 性能问题 .............................................229

8.1.2 记忆化的优点 .....................................230

8.1.3 记忆化的缺点 .....................................230

8.1.4 实际性能 .............................................232

8.2 复合List和Result .............................................233

8.2.1 List中返回 Result的方法 ..................233

8.2.2 将 List转换为 Result 235

8.3 抽象常见列表用例 .........................................238

8.3.1 压缩和解压缩列表 .............................238

8.3.2 通过索引访问元素 .............................241

8.3.3 拆分列表 .............................................244

8.3.4 搜索子列表 .........................................248

8.3.5 使用列表的其他函数 .........................249

8.4 自动并行处理列表 .........................................254

8.4.1 并非所有的计算都可以并行化 .........254

8.4.2 将列表拆分为子列表 .........................254

8.4.3 并行处理子列表 .................................256

8.5 总结 ...........258

第9章 使用惰性....................................... 259

9.1 理解严格和惰性 .............................................259

9.1.1 Java是一门严格的语言 .....................260

9.1.2 严格带来的问题 .................................261

9.2 实现惰性 ...263

9.3 只有惰性才能做到的事 .................................264

9.4 为何不要用Java 8中的Stream ........................265

9.5 创建一个惰性列表数据结构 .........................266

9.5.1 记忆已计算的值 .................................268

9.5.2 对流的操作 .........................................271

9.6 惰性的真正本质 .............................................274

9.6.1 折叠流 .................................................277

9.7 处理无限流 .....................................................282

9.8 避免null引用和可变字段 ...............................285

9.9 总结 ...........287

第10章 用树进行更多数据处理.................. 289

10.1 二叉树 .....290

10.1.1 平衡树和非平衡树 .........................291

10.1.2 大小、高度和深度 .........................291

10.1.3 叶树 .................................................292

10.1.4 有序二叉树或二叉搜索树 .............292

10.1.5 插入顺序 .........................................293

10.1.6 树的遍历顺序 .................................294

10.2 实现二叉搜索树 ...........................................297

10.3 从树中删除元素 ...........................................303

10.4 合并任意树 ...................................................304

10.5 折叠树 .....310

10.5.1 用两个函数折叠 .............................311

10.5.2 用一个函数折叠 .............................313

10.5.3 选择哪种折叠的实现 .....................314

10.6 映射树 .....316

10.7 平衡树 .....317

10.7.1 旋转树 .............................................317

10.7.2 使用 DSW算法平衡树 ..................320

10.7.3 自动平衡树 .....................................322

10.7.4 解决正确的问题 .............................323

10.8 总结 .........324

第11章 用高级树来解决真实问题............... 325

11.1 性能更好且栈安全的自平衡树 ....................326

11.1.1 树的基本结构 .................................326

11.1.2 往红黑树中插入元素 .....................331

11.2 红黑树的用例:map .....................................337

11.2.1 实现 map .........................................337

11.2.2 扩展 map .........................................340

11.2.3 使用键不可比较的 map .................341

11.3 实现函数式优先队列 ....................................344

11.3.1 优先队列访问协议 .........................344

11.3.2 优先队列使用案例 .........................344

11.3.3 实现需求 .........................................345

11.3.4 左倾堆数据结构 .............................345

11.3.5 实现左倾堆 .....................................346

11.3.6 实现像队列一样的接口 .................351

11.4 元素不可比较的优先队列 ............................352

11.5 总结 ..........358

第12章 用函数式的方式处理状态改变 ........ 359

12.1 一个函数式的随机数发生器 .......................360

12.1.1 随机数发生器接口 .........................361

12.1.2 实现随机数发生器 .........................362

12.2 处理状态的通用API .....................................366

12.2.1 使用状态操作 .................................367

12.2.2 复合状态操作 .................................368

12.2.3 递归状态操作 .................................370

12.3 通用状态处理 ...............................................372

12.3.1 状态模式 .........................................374

12.3.2 构建一个状态机 .............................375

12.3.3 何时使用状态和状态机 .................381

12.4 总结 .........381

第13章 函数式输入/输出.......................... 382

13.1 在上下文中应用作用 ...................................383

13.1.1 作用是什么 .....................................384

13.1.2 实现作用 .........................................384

13.1.3 用于失败情况的更强大的作用 .....387

13.2 读取数据 .390

13.2.1 从控制台读取 .................................390

13.2.2 从文件中读取 .................................395

13.2.3 检查输入 .........................................396

13.3 真正的函数式输入/输出 ..............................398

13.3.1 怎样才能让输入 /输出是完全函数式的 ............................................398

13.3.2 实现纯函数式的输入 /输出 ..........399

13.3.3 合并 IO............................................400

13.3.4 用 IO处理输入...............................402

13.3.5 扩展 IO类型...................................404

13.3.6 使 IO类型栈安全...........................407

13.4 总结 .........413

第14章 通过actor共享可变状态................. 414

14.1 actor模型 .415

14.1.1 异步消息 .........................................416

14.1.2 处理并行 .........................................416

14.1.3 处理 actor状态变化 .......................417

14.2 构建actor框架 ...............................................418

14.2.1 actor框架的限制 ............................418

14.2.2 设计 actor框架接口 .......................418

14.2.3 AbstractActor的实现 .....................420

14.3 开始使用actor ...............................................422

14.3.1 实现乒乓示例 .................................422

14.3.2 一个更严谨的例子:并行运行一个计算 ...........................................424

14.3.3 重新排序结果 .................................430

14.3.4 解决性能问题 .................................433

14.4 总结 .........439

第15章 以函数式的方式解决常见问题 ........ 440

15.1 使用断言来校验数据 ...................................441

15.2 从文件中读取属性 .......................................446

15.2.1 载入属性文件 .................................446

15.2.2 将属性读取为字符串 .....................447

15.2.3 生成更好的错误消息 .....................448

15.2.4 像列表那样读取属性 .....................451

15.2.5 读取枚举值 .....................................453

15.2.6 读取任意类型的属性 .....................454

15.3 转换命令式程序:XML读取器 ...................457

15.3.1 列出必需的函数 .............................459

15.3.2 复合函数并应用作用 .....................460

15.3.3 实现函数 .........................................461

15.3.4 让程序更加函数式 .........................462

15.3.5 修复参数类型问题 .........................466

15.3.6 以处理元素的函数为参数 .............467

15.3.7 处理元素名称错误 .........................468

15.4 总结 .........470

附录A 使用Java 8的函数式特性 ................ 471

附录B Monad......................................... 479

附录C 敢问路在何方 ............................... 485

读者评论

  • 203页 注释里面:IllegalStateException应该是RuntimeException的子类吧

    mourinholiu发表于 2019/4/11 23:33:38
  • 不是找到错误,而是不太懂j**a里面的泛型机制:7.2节 Either类型中的答案7.1:public <B> Either<E,B> map(Function<A,B> f){return new Left<>(value)},这个map返回的是Either<E,B>,可是new Left<>(value)返回的是Either<E,A>?

    mourinholiu发表于 2019/4/11 23:31:54

相关图书

Python一行流:像专家一样写代码

Christian Mayer (作者) 苏丹 (译者)

本书专注于从初学迈向进阶的Python编码技术:如何像专家一样写出优雅、准确、简洁高效的Python 单行代码;阅读任意一行Python 代码时,如何系统性地对...

¥89.00

剑指Offer(专项强化版):数据结构与算法名企面试题精讲

何海涛 (作者)

本书全面、系统地总结了在准备程序员面试过程中必备的数据结构与算法。本书首先详细讨论整数、数组、链表、字符串、哈希表、栈、队列、二叉树、堆和前缀树等常用的数据结构...

¥89.00

C++服务器开发精髓

张远龙 (作者)

本书从操作系统原理角度讲解进行C++服务器开发所需掌握的技术栈。全书总计9章,第1~2章讲解C++ 11/14/17新标准中的常用特性、新增类库,以及C++开发...

 

PHP编程(第4版)

Rasmus Lerdorf(拉斯马斯·勒多夫), Kevin Tatroe(凯文·塔特罗), Peter MacIntyre(彼得·麦金太尔) (作者) 卢涛 (译者)

PHP是一种被广泛使用的Web编程语言,它简单易用,而且与时俱进,不断进化,提升性能。本书基于PHP 7.4,深入介绍 PHP编程技术,涵盖了 PHP 所有基本...

¥109.00

深入理解Spring Cloud与实战

方剑 (作者)

本书共分10章,主要介绍Spring Cloud各个核心组件的设计原理,以及目前流行的Spring Cloud Alibaba和 Netflix组件,并且剖析S...

¥106.00

基于Kotlin的Spring Boot微服务实战

袁康 (作者)

本书介绍了Kotlin在Spring Boot微服务开发中的实践,并使用Kotlin作为开发语言,介绍了函数式编程思想、Kotlin的语法、Kotlin在常用中...

¥69.30