《Java8实战》笔记上篇

链接: [Java8实战] (链接:https://pan.baidu.com/s/1o2qqBbmjQ_qbXti6qcYv5g 提取码:java ).

1 小结

没有共享的可变数据,将方法和函数即代码传递给其他方法的能力)是我们平常所说的函数式编程范式的基石.

2.5 小结

以下是你应从本章中学到的关键概念。
 行为参数化,就是一个方法接受多个不同的行为作为参数,并在内部使用它们,完成不
同行为的能力。
 行为参数化可让代码更好地适应不断变化的要求,减轻未来的工作量。
 传递代码,就是将新行为作为参数传递给方法。但在Java 8之前这实现起来很啰嗦。为接
口声明许多只用一次的实体类而造成的啰嗦代码,在Java 8之前可以用匿名类来减少。
 Java API包含很多可以用不同行为进行参数化的方法,包括排序、线程和GUI处理。

3.10 小结

以下是你应从本章中学到的关键概念。
 Lambda表达式可以理解为一种匿名函数:它没有名称,但有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常的列表。
 Lambda表达式让你可以简洁地传递代码。
 函数式接口就是仅仅声明了一个抽象方法的接口。
 只有在接受函数式接口的地方才可以使用Lambda表达式。
 Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例。
 Java 8自带一些常用的函数式接口,放在java.util.function包里,包括Predicate、Function<T,R>、Supplier、Consumer和BinaryOperator,如表3-2所述。
 为了避免装箱操作,对Predicate和Function<T, R>等通用函数式接口的原始类型特化:IntPredicate、IntToLongFunction等。
 环绕执行模式(即在方法所必需的代码中间,你需要执行点儿什么操作,比如资源分配和清理)可以配合Lambda提高灵活性和可重用性。
 Lambda表达式所需要代表的类型称为目标类型。
 方法引用让你重复使用现有的方法实现并直接传递它们。
 Comparator、Predicate和Function等函数式接口都有几个可以用来结合Lambda表达式的默认方法

4.5 小结

以下是你应从本章中学到的一些关键概念。
 流是“从支持数据处理操作的源生成的一系列元素”。
 流利用内部迭代:迭代通过filter、map、sorted等操作被抽象掉了。
 流操作有两类:中间操作和终端操作。
 filter和map等中间操作会返回一个流,并可以链接在一起。可以用它们来设置一条流水线,但并不会生成任何结果。
 forEach和count等终端操作会返回一个非流的值,并处理流水线以返回结果。
 流中的元素是按需计算的。

5.8 小结

这一章很长,但是很有收获!现在你可以更高效地处理集合了。事实上,流让你可以简洁地表达复杂的数据处理查询。此外,流可以透明地并行化。以下是你应从本章中学到的关键概念。
 Streams API可以表达复杂的数据处理查询。常用的流操作总结在表5-1中。
 你可以使用filter、distinct、skip和limit对流做筛选和切片。
 你可以使用map和flatMap提取或转换流中的元素。
 你可以使用findFirst和 findAny方法查找流中的元素。你可以用allMatch、noneMatch和anyMatch方法让流匹配给定的谓词。
 这些方法都利用了短路:找到结果就立即停止计算;没有必要处理整个流。
 你可以利用reduce方法将流中所有的元素迭代合并成一个结果,例如求和或查找最大
元素。
 filter和map等操作是无状态的,它们并不存储任何状态。reduce等操作要存储状态才能计算出一个值。sorted和distinct等操作也要存储状态,因为它们需要把流中的所有元素缓存起来才能返回一个新的流。这种操作称为有状态操作。
 流有三种基本的原始类型特化:IntStream、DoubleStream和LongStream。它们的操作也有相应的特化。
 流不仅可以从集合创建,也可从值、数组、文件以及iterate与generate等特定方法创建。
 无限流是没有固定大小的流。

6.7 小结

以下是你应从本章中学到的关键概念。
 collect是一个终端操作,它接受的参数是将流中元素累积到汇总结果的各种方式(称
为收集器)。
 预定义收集器包括将流元素归约和汇总到一个值,例如计算最小值、最大值或平均值。
这些收集器总结在表6-1中。
 预定义收集器可以用groupingBy对流中元素进行分组,或用partitioningBy进行分区。
 收集器可以高效地复合起来,进行多级分组、分区和归约。
 你可以实现Collector接口中定义的方法来开发你自己的收集器。

 如果有疑问,测量。把顺序流转成并行流轻而易举,但却不一定是好事。我们在本节中已经指出,并行流并不总是比顺序流快。此外,并行流有时候会和你的直觉不一致,所以在考虑选择顺序流还是并行流时,第一个也是最重要的建议就是用适当的基准来检查其性能。
 留意装箱。自动装箱和拆箱操作会大大降低性能。Java 8中有原始类型流(IntStream、LongStream、DoubleStream)来避免这种操作,但凡有可能都应该用这些流。
 有些操作本身在并行流上的性能就比顺序流差。特别是limit和findFirst等依赖于元素顺序的操作,它们在并行流上执行的代价非常大。例如,findAny会比findFirst性能好,因为它不一定要按顺序来执行。你总是可以调用unordered方法来把有序流变成无序流。那么,如果你需要流中的n个元素而不是专门要前n个的话,对无序并行流调用limit可能会比单个有序流(比如数据源是一个List)更高效。
 还要考虑流的操作流水线的总计算成本。设N是要处理的元素的总数,Q是一个元素通过
流水线的大致处理成本,则N*Q就是这个对成本的一个粗略的定性估计。Q值较高就意味
着使用并行流时性能好的可能性比较大。
 对于较小的数据量,选择并行流几乎从来都不是一个好的决定。并行处理少数几个元素的好处还抵不上并行化造成的额外开销。
 要考虑流背后的数据结构是否易于分解。例如,ArrayList的拆分效率比LinkedList高得多,因为前者用不着遍历就可以平均拆分,而后者则必须遍历。另外,用range工厂方法创建的原始类型流也可以快速分解。最后,你将在7.3节中学到,你可以自己实现Spliterator来完全掌控分解过程。
 流自身的特点,以及流水线中的中间操作修改流的方式,都可能会改变分解过程的性能。例如,一个SIZED流可以分成大小相等的两部分,这样每个部分都可以比较高效地并行处理,但筛选操作可能丢弃的元素个数却无法预测,导致流本身的大小未知。
 还要考虑终端操作中合并步骤的代价是大是小(例如Collector中的combiner方法)。如果这一步代价很大,那么组合每个子流产生的部分结果所付出的代价就可能会超出通过并行流得到的性能提升。

7.2.2 使用分支/合并框架的最佳做法

虽然分支/合并框架还算简单易用,不幸的是它也很容易被误用。以下是几个有效使用它的最佳做法。
 对一个任务调用join方法会阻塞调用方,直到该任务做出结果。因此,有必要在两个子任务的计算都开始之后再调用它。否则,你得到的版本会比原始的顺序算法更慢更复杂,因为每个子任务都必须等待另一个子任务完成才能启动。
 不应该在RecursiveTask内部使用ForkJoinPool的invoke方法。相反,你应该始终直接调用compute或fork方法,只有顺序代码才应该用invoke来启动并行计算。
 对子任务调用fork方法可以把它排进ForkJoinPool。同时对左边和右边的子任务调用它似乎很自然,但这样做的效率要比直接对其中一个调用compute低。这样做你可以为其中一个子任务重用同一线程,从而避免在线程池中多分配一个任务造成的开销。
 调试使用分支/合并框架的并行计算可能有点棘手。特别是你平常都在你喜欢的IDE里面看栈跟踪(stack trace)来找问题,但放在分支合并计算上就不行了,因为调用compute的线程并不是概念上的调用方,后者是调用fork的那个。
 和并行流一样,你不应理所当然地认为在多核处理器上使用分支/合并框架就比顺序计算快。我们已经说过,一个任务可以分解成多个独立的子任务,才能让性能在并行化时有所提升。所有这些子任务的运行时间都应该比分出新任务所花的时间长;一个惯用方法是把输入/输出放在一个子任务里,计算放在另一个里,这样计算就可以和输入/输出同时进行。此外,在比较同一算法的顺序和并行版本的性能时还有别的因素要考虑。就像任何其他Java代码一样,分支/合并框架需要“预热”或者说要执行几遍才会被JIT编
译器优化。这就是为什么在测量性能之前跑几遍程序很重要,我们的测试框架就是这么做的。同时还要知道,编译器内置的优化可能会为顺序版本带来一些优势(例如执行死码分析——删去从未被使用的计算)。对于分支/合并拆分策略还有最后一点补充:你必须选择一个标准,来决定任务是要进一步拆分还是已小到可以顺序求值。我们会在下一节中就此给出一些提示。

7.4 小结

在本章中,你了解了以下内容。
 内部迭代让你可以并行处理一个流,而无需在代码中显式使用和协调不同的线程。
 虽然并行处理一个流很容易,却不能保证程序在所有情况下都运行得更快。并行软件的行为和性能有时是违反直觉的,因此一定要测量,确保你并没有把程序拖得更慢。
 像并行流那样对一个数据集并行执行操作可以提升性能,特别是要处理的元素数量庞大,或处理单个元素特别耗时的时候。
 从性能角度来看,使用正确的数据结构,如尽可能利用原始流而不是一般化的流,几乎总是比尝试并行化某些操作更为重要。
 分支/合并框架让你得以用递归方式将可以并行的任务拆分成更小的任务,在不同的线程上执行,然后将各个子任务的结果合并起来生成整体结果。
 Spliterator定义了并行流如何拆分它要遍历的数据。

8.5 小结

下面回顾一下这一章的主要内容。
 Lambda表达式能提升代码的可读性和灵活性。
 如果你的代码中使用了匿名类,尽量用Lambda表达式替换它们,但是要注意二者间语义的微妙差别,比如关键字this,以及变量隐藏。
 跟Lambda表达式比起来,方法引用的可读性更好 。
 尽量使用Stream API替换迭代式的集合处理。
 Lambda表达式有助于避免使用面向对象设计模式时容易出现的僵化的模板代码,典型的
比如策略模式、模板方法、观察者模式、责任链模式,以及工厂模式。
 即使采用了Lambda表达式,也同样可以进行单元测试,但是通常你应该关注使用了Lambda表达式的方法的行为。
 尽量将复杂的Lambda表达式抽象到普通方法中。
 Lambda表达式会让栈跟踪的分析变得更为复杂。
 流提供的peek方法在分析Stream流水线时,能将中间变量的值输出到日志中,是非常有用的工具。

Java 8中的抽象类和抽象接口
那么抽象类和抽象接口之间的区别是什么呢?它们不都能包含抽象方法和包含方法体的实现吗?
首先,一个类只能继承一个抽象类,但是一个类可以实现多个接口。
其次,一个抽象类可以通过实例变量(字段)保存一个通用状态,而接口是不能有实例变量的。

菱形继承问题中一个类同时继承了具有相同函数签名的两个方法。到底该选择哪一个实现呢?

9.4.1 解决问题的三条规则

如果一个类使用相同的函数签名从多个地方(比如另一个类或接口)继承了方法,通过三条规则可以进行判断。
(1) 类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
(2) 如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体。
(3) 最后,如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法,显式地选择使用哪一个默认方法的实现。

现在你应该已经了解了,如果一个类的默认方法使用相同的函数签名继承自多个接口,解决冲突的机制其实相当简单。你只需要遵守下面这三条准则就能解决所有可能的冲突。
 首先,类或父类中显式声明的方法,其优先级高于所有的默认方法。
 如果用第一条无法判断,方法签名又没有区别,那么选择提供最具体实现的默认方法的
接口。
 最后,如果冲突依旧无法解决,你就只能在你的类中覆盖该默认方法,显式地指定在你的类中使用哪一个接口中的方法。

9.5 小结

下面是本章你应该掌握的关键概念。
 Java 8中的接口可以通过默认方法和静态方法提供方法的代码实现。
 默认方法的开头以关键字default修饰,方法体与常规的类方法相同。
 向发布的接口添加抽象方法不是源码兼容的。
 默认方法的出现能帮助库的设计者以后向兼容的方式演进API。
 默认方法可以用于创建可选方法和行为的多继承。
 我们有办法解决由于一个类从多个接口中继承了拥有相同函数签名的方法而导致的冲突。
 类或者父类中声明的方法的优先级高于任何默认方法。如果前一条无法解决冲突,那就选择同函数签名的方法中实现得最具体的那个接口的方法。
 两个默认方法都同样具体时,你需要在类中覆盖该方法,显式地选择使用哪个接口中提供的默认方法。

使用流时,flatMap方法接受一个函数作为参数,这个函数的返回值是另一个流。这个方法会应用到流中的每一个元素,最终形成一个新的流的流。但是flagMap会用流的内容替换每个新生成的流。换句话说,由方法生成的各个流会被合并或者扁平化为一个单一的流。这里你希望的结果其实也是类似的,但是你想要的是将两层的optional合并为一个。

在这里插入图片描述

10.5 小结

这一章中,你学到了以下的内容。
 null引用在历史上被引入到程序设计语言中,目的是为了表示变量值的缺失。
 Java 8中引入了一个新的类java.util.Optional,对存在或缺失的变量值进行建模。
 你可以使用静态工厂方法Optional.empty、Optional.of以及Optional.ofNullable创建Optional对象。
 Optional类支持多种方法,比如map、flatMap、filter,它们在概念上与Stream类中对应的方法十分相似。
 使用Optional会迫使你更积极地解引用Optional对象,以应对变量值缺失的问题,最终,你能更有效地防止代码中出现不期而至的空指针异常。
 使用Optional能帮助你设计更好的API,用户只需要阅读方法签名,就能了解该方法是否接受一个Optional类型的值。
10.5 小结
这一章中,你学到了以下的内容。
 null引用在历史上被引入到程序设计语言中,目的是为了表示变量值的缺失。
 Java 8中引入了一个新的类java.util.Optional,对存在或缺失的变量值进行
建模。
 你可以使用静态工厂方法Optional.empty、Optional.of以及Optional.ofNullable创建Optional对象。
 Optional类支持多种方法,比如map、flatMap、filter,它们在概念上与Stream类中对应的方法十分相似。
 使用Optional会迫使你更积极地解引用Optional对象,以应对变量值缺失的问题,最终,你能更有效地防止代码中出现不期而至的空指针异常。
 使用Optional能帮助你设计更好的API,用户只需要阅读方法签名,就能了解该方法是否接受一个Optional类型的值。

10.5 小结
这一章中,你学到了以下的内容。
 null引用在历史上被引入到程序设计语言中,目的是为了表示变量值的缺失。
 Java 8中引入了一个新的类java.util.Optional,对存在或缺失的变量值进行建模。
 你可以使用静态工厂方法Optional.empty、Optional.of以及Optional.ofNullable创建Optional对象。
 Optional类支持多种方法,比如map、flatMap、filter,它们在概念上与Stream类中对应的方法十分相似。
 使用Optional会迫使你更积极地解引用Optional对象,以应对变量值缺失的问题,最终,你能更有效地防止代码中出现不期而至的空指针异常。
 使用Optional能帮助你设计更好的API,用户只需要阅读方法签名,就能了解该方法是否接受一个Optional类型的值。

在这里插入图片描述

大自然的搬运工,欢迎点赞分享

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值