最近开始研究Java8,现将其体验分享如下:
1.为什么需要使用Java8
1996 年1 月,Java 1.0 发布,此后计算机编程领域发生了翻天覆地的变化。商业发展需要更复杂的应用,大多数程序都跑在功能强大的多核CPU 的机器上。带有高效运行时编译器的Java 虚拟机(JVM)的出现,使程序员将更多精力放在编写干净、易于维护的代码上,而不是思考如何将每一个CPU 时钟周期、每字节内存物尽其用。多核CPU 的兴起成为了不容回避的事实。涉及锁的编程算法不但容易出错,而且耗费时间。人们开发了java.util.concurrent 包和很多第三方类库,试图将并发抽象化,帮助程序员写出在多核CPU 上运行良好的程序。很可惜,到目前为止,我们的成果还远远不够。
开发类库的程序员使用Java 时,发现抽象级别还不够。处理大型数据集合就是个很好的例子,面对大型数据集合,Java 还欠缺高效的并行操作。开发者能够使用Java 8 编写复杂的集合处理算法,只需要简单修改一个方法,就能让代码在多核CPU 上高效运行。为了编写这类处理批量数据的并行类库,需要在语言层面上修改现有的Java:增加Lambda 表达式。
当然,这样做是有代价的,程序员必须学习如何编写和阅读使用Lambda 表达式的代码,但是,这不是一桩赔本的买卖。与手写一大段复杂、线程安全的代码相比,学习一点新语法和一些新习惯容易很多。开发企业级应用时,好的类库和框架极大地降低了开发时间和成本,也为开发易用且高效的类库扫清了障碍。
对于习惯了面向对象编程的开发者来说,抽象的概念并不陌生。面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象。现实世界中,数据和行为并存,程序也是如此,因此这两种编程方式我们都得学。这种新的抽象方式还有其他好处。不是所有人都在编写性能优先的代码,对于这些人来说,函数式编程带来的好处尤为明显。程序员能编写出更容易阅读的代码——这种代码更多地表达了业务逻辑的意图,而不是它的实现机制。易读的代码也易于维护、更可靠、更不容易出错。
在写回调函数和事件处理程序时,程序员不必再纠缠于匿名内部类的冗繁和可读性,函数式编程让事件处理系统变得更加简单。能将函数方便地传递也让编写惰性代码变得容易,惰性代码在真正需要时才初始化变量的值。Java 8 还让集合类可以拥有一些额外的方法:default 方法。程序员在维护自己的类库时,可以使用这些方法。
2.函数式编程
每个人对函数式编程的理解不尽相同。但其核心是:在思考问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值。不同的语言社区往往对各自语言中的特性孤芳自赏。现在谈Java 程序员如何定义函数式编程还为时尚早,但是,这根本不重要!我们关心的是如何写出好代码,而不是符合函数式编程风格的代码。本书将重点放在函数式编程的实用性上,包括可以被大多数程序员理解和使用的技术,帮助他们写出易读、易维护的代码。
3.Lambda表达式
Lambda 表达式是 一个匿名方法,将行为像数据一样进行传递。
A.Lambda表达式的常见结构:BinaryOperatoradd = (x, y) → x + y。
B.函数接口指仅具有单个抽象方法的接口,用来表示 Lambda表达式的类型。
4.流
内部迭代将更多控制权交给了集合类。
A.和 Iterator 类似,Stream 是一种内部迭代方式。
B.将 Lambda表达式和 Stream 上的方法结合起来,可以完成很多常见的集合操作。
5.类库
使用为基本类型定 制的 Lambda表达式和 Stream,如IntStream 可以显著提升系统性能。
A.默认方法是指接口中定义的包含方法体的方法,方法名有 default 关键字做前缀。
B.在一个值可能为空的建模情况下,使用 Optional 对象能替代使用 null 值。
6.高级集合类和收集器
方法引用是一种引用方法的轻量级语法,形如:ClassName::methodName。
A.收集器可用来计算流的最终值,是 reduce 方法的模拟。
B.Java 8 提供了收集多种容器类型的方式,同时允许用户自定义收集器。
7.数据并行化
数据并行化是把 工作拆分,同时在多核 CPU上执行的方式。
A.如果使用流编写代码,可通过调用 parallel 或者 parallelStream 方法实现数据并行化操作。
B. 影响性能的五要素是:数据大小、源数据结构、值是否装箱、可用的 CPU 核数量,以及处理每个元素所花的时间。
8.重构代码和测试
重构遗留代码时考虑如何使用 Lambda 表达式,有一些通用的模式。
A.如果想要对复杂一点的 Lambda表达式编写单元测试,将其抽取成一个常规的方法。
B.peek 方法能记录中间值,在调试时非常有用。
9.设计架构和原则
Lambda 表达式能让很多现有设计模 式更简单、可读性更强,尤其是命令者模式。
A.在 Java 8 中,创建领域专用语言有更多的灵活性。
B.在 Java 8 中,有应用 SOLID 原则的新机会。
10.关于响应式编程RxJava
构建复杂并行操作的另外一种方案是使用Future。Future 像一张欠条,方法不是返回一个值,而是返回一个Future 对象,该对象第一次创建时没有值,但以后能拿它“换回”一个值。调用Future 对象的get 方法获取值,它会阻塞当前线程,直到返回值。可惜,和回调一样,组合Future 对象时也有问题,我们会快速浏览这些可能碰到的问题。
这些问题的解决之道是CompletableFuture,它结合了Future 对象打欠条的主意和使用回调处理事件驱动的任务。其要点是可以组合不同的实例,而不用担心末日金字塔问题。
CompletableFuture 背后的概念可以从单一的返回值推广到数据流,这就是响应式编程。响应式编程其实是一种声明式编程方法,它让程序员以自动流动的变化和数据流来编程。你可以将电子表格想象成一个使用响应式编程的例子。如果在单元格C1 中键入=B1+5,其实是在告诉电子表格将B1 中的值加5,然后将结果存入C1。而且,将来B1 中的值变化后,电子表格会自动刷新C1 中的值。
RxJava 类库将这种响应式的理念移植到了JVM。我们这里不会深入类库,只描述其中的一些关键概念。
RxJava 类库引入了一个叫作Observable 的类,该类代表了一组待响应的事件,可以理解为一沓欠条。在Observable 对象和第3 章讲述的Stream 接口之间有很强的关联。
两种情况下,都需要使用Lambda 表达式将行为和一般的操作关联、都需要将高阶函数链接起来定义完成任务的规则。实际上,Observable 定义的很多操作都和Stream 的相同:map、filter、reduce。
最大的不同在于用例。Stream 是为构建内存中集合的计算流程而设计的,而RxJava 则是为了组合异步和基于事件的系统流程而设计的。它没有取数据,而是把数据放进去。换个角度理解RxJava,它是处理一组值,而CompletableFuture 用来处理一个值。
11.何时使用新技术
新技术那么美好,这是否意味着大家明天就要扔掉现有的Java EE 或者Spring 企业级Web 应用呢?答案当然是否定的。
即使不去考虑CompletableFuture 和RxJava 相对较新,使用它们依然有一定的复杂度。它们用起来比到处显式使用Future 和回调简单,但对很多问题来说,传统的阻塞式Web 应用开发技术就足够了。如果还能用,就别修理。
事件驱动和响应式应用正在变得越来越流行,而且经常会是为你的问题建模的最好方式之一。响应式编程宣言(http://www.reactivemanifesto.org/)鼓励大家使用这种方式编写更多应用,如果它适合你的待解问题,那么就应该使用。相比阻塞式设计,有两种情况可能特别适合使用响应式或事件驱动的方式来思考。
第一种情况是业务逻辑本身就使用事件来描述。Twitter 就是一个经典例子。Twitter 是一种订阅文字流信息的服务,用户彼此之间推送信息。使用事件驱动架构编写应用,能准确地为业务建模。图形化展示股票价格可能是另一个例子,每一次价格的变动都可认为是一个事件。
另一种显然的用例是应用需要同时处理大量I/O 操作。阻塞式I/O 需要同时使用大量线程,这会导致大量锁之间的竞争和太多的上下文切换。如果想要处理成千上万的连接,非阻塞式I/O 通常是更好的选择。
12.使用Lambda表达式编写并发程序
使用基于Lambda 表达式的回调,很容易实现事件驱动架构。
A.CompletableFuture 代表了 IOU,使用 Lambda表达式能方便地组合、合并。
B.Observable 继承了 CompletableFuture 的概念,用来处理数据流。
13.下一步计划
A. 向其他程序员(朋友或同事)解释什么是Lambda 表达式,为什么会对它产生兴趣。
B.尝试将目前从事的项目部署到 Java 8 环境下。如果现有单元测试已经能运行在持续集成系统Jenkins 下,那么在多个版本的Java 上构建程序也易如反掌。
C. 使用新的 Stream 和 Collector,开始重构真实产品中的遗留代码。它既可以是感兴趣的开放源码项目,也可以是当前从事的项目,前提是第一步里已经部署成功一个测试环境。
D.如果还没准备好大规模迁往Java 8,那么在分支上使用Java 8 做一些原型会是个不错的开始。
E.有没有一些大规模处理数据的代码?或者代码中存在并发问题?试着使用 Stream 处理数据,或使用RxJava 中新的并发特性,也可以使用CompletableFuture 类,来重构你的代码。
F.选择一个熟悉的代码库,分析它的设计和架构。
从宏观上看,有没有更好的实现方法?
能否简化设计?
能否减少实现某功能所需的代码量?
怎样让代码更易读?
网友评论