可瑞生物完成1亿元Pre-A+轮融资,由阳光聚变领投、馨瑞医疗
06-18
第 42 条 与匿名类相比,更喜欢 lambda。以前,只有抽象方法接口(或抽象类)被用作函数类型。
它们的实例是函数对象,代表函数或行为。从JDK1.1开始,创建函数对象的主要行为是匿名类。
匿名类非常适合需要函数对象的经典设计模式,例如策略模式。例如->Comparator提供抽象策略,匿名类实现具体策略。
在 Java 8 中,人们认为只有一个抽象方法的接口值得特殊对待。它们现在称为函数式接口,并且该语言允许您使用 lambda 表达式创建这些接口的实例。
Lambda 的功能与匿名类类似。 ,但更简洁。
Lambda 表达式可以省略参数和返回值的类型,因为编译器会通过类型推断来推导它们。 (需要指定何时无法推导。
) 示例:之前的每个枚举行为 不同的示例可以使用 lambda 进行简化。构造时传递 lambda 并用 DoubleBinaryOperator 保存。
注意:由于 lambda 没有名称和文档,如果一个计算不言自明或者有大量行(对于 lambda 来说,一行最好,三行最多),不要将其放在 lambda 中。通过 enum 构造传入的参数处于静态环境中,因此从 enum 构造传入的 lambda 无法访问枚举的成员变量。
还有一些事情是,匿名类可以做事情,但是不能用 lambda 来代替:lambda 只限于函数式接口(functionalinterface),所以抽象类和具有多个方法的接口仍然需要使用匿名类。在 lambda 中,您无法获得自己的引用,此关键字引用封闭的实例。
匿名类中的 this 指的是匿名类对象。 lambda 和匿名类都无法可靠地序列化和反序列化,因此您应该减少这样做。
如果确实有必要的话,请使用私有静态内部类的实例。第 43 项:使用方法引用而不是 lambda。
使用 lambda 替换匿名类的主要优点是简单。事实上,Java还提供了一种更简洁的方式来生成函数对象:方法引用(methodreferences)。
代码示例:统计单词数的方法,使用Map接口的merge方法(Java 8): 代码语言:txt copy map.merge(key, 1, (count, incr) -> count + incr);改进:代码语言:txt copy map.merge(key, 1, Integer ::sum);为了更简洁,可以将lambda中的代码提取到一个新的方法中,然后用这个方法引用来替换原始的 lambda。但有时,lambda 比方法引用更简洁。
例如:method和lambda在同一个类中。许多方法引用是静态的,但有四种非静态方法:绑定和非绑定实例方法引用、类和数组的构造函数引用(作为工厂对象)。
结论:当方法引用比lambda更简洁时,就使用方法引用,否则使用lambda。第44条:优先使用标准功能接口。
有了 lambda,模板方法(Template Method)模式就不再有吸引力了。现代方法是提供一个接收函数对象的静态工厂。
或者构造函数来达到同样的效果。更一般地说,您需要编写更多以函数对象作为参数的构造函数和方法。
请小心选择正确的函数参数类型。 java.util.function包提供了一系列标准函数接口(总共43个)。
六个基本函数接口: UnaryOperator
Predicate
Supplier
每个基本接口都有 3 个变体,分别对应基本类型:int、long 和 double。接口名称将以类型为前缀。
(6*3=18) Function接口也有9种变体,对应不同的基本结果类型。类型。
如果源和结果都是基本类型,则添加前缀SrcToResult,例如LongToIntFunction。 (6种)。
如果源是基本类型,则结果是对象引用,添加前缀SrcToObj,例如DoubleToObjFunction。 (3种)。
共有 3 种双参数版本的基本函数式接口,共有 9 种变体: BiPredicate
(4 种)。 BiConsumer
(4 种)。最后,还有 BooleanSupplier,它是返回布尔值的供应商变体。
大多数标准函数接口的存在只是为了提供对原始类型的支持。不要使用带有盒装原语的基本功能接口来代替基本功能接口。
原始函数接口)。那么什么时候应该编写自己的函数式接口呢?没有能够满足需求的标准功能接口。
-> 比如参数个数不同,或者抛出受检异常。有具有相同结构的标准函数 在某些情况下,您应该编写自己的函数接口。
例如,Comparator
如果您想编写自己的函数式接口而不是使用标准函数式接口,则必须考虑它是否具有(一项或多项)以下功能,例如 Comparator: 它是通用的,并且受益于具有描述性的名称。它与协议有很强的相关性。
它将受益于自定义默认方法。始终记住使用 @FunctionalInterface 注释来标记您的功能接口。
提供方法重载时,注意不要在同一个参数位置对同一个方法提供功能接口(可能会引起歧义)。例如:ExecutorService的submit方法。
这需要强制客户端代码指示正确的重载。第 45 条 谨慎使用 StreamsStream API 引入 Java 8 中的新功能 Streams API 主要是为了让执行批量操作(串行或并行)变得更加容易。
两个关键的抽象: 流:数据元素的有限或无限序列。 (支持对象引用或基本类型:int、long、double).stream pipeline:对这些元素进行多级操作。
(0 个或多个中间操作和 1 个终端操作)。流管道的操作是惰性的,只有当终端操作被触发时才会开始操作。
,最终操作不需要的数据将不会被操作(可以无限数据)。 Streams API 是流式传输。
为什么要谨慎使用 Streams。正确使用streams API可以使程序更加简洁,但使用不当(过度使用)可能会降低可读性和可维护性。
示例:查找字谜(具有相同字母但顺序不同的单词)。 Java 8 中的新方法:computeIfAbsent。
关于流管道的可读性:当缺少显式类型时,良好的 lambda 参数命名是必要的。使用辅助方法,提取逻辑并命名。
不要使用stream来处理char值,因为它们是int值并且需要进行强制转换。 Stream与循环迭代的比较 流管道使用函数对象(function object),通常是lambda和方法引用;循环迭代(迭代代码)使用代码块。
代码块可以做函数对象不能做的事情(循环可以做到,但流不能):代码块可以读取和修改作用域。任何局部变量; lambda 中只能读取最终变量,并且不能修改局部变量。
在循环代码块中,可以返回、中断或继续,并抛出方法声明的已检查异常; Stream 擅长的这些事情 lambda 无法做到。 :元素序列的统一处理。
过滤。对组合元素的操作(相加、连接、计算最小值等)。
将元素序列累积到集合或分组中。询问。
使用流时比较困难的一件事是,在管道处理过程中,很难找到对应的原始元素:一旦映射了新值,之前的值就会丢失。与lame相比,做法是使用pair,需要原始值时还可以考虑反向映射。
示例:打印 20 个梅森素数:2 的 n 次方仍然是素数。现在我想找到对应的n -> 添加一个反向操作。
有时迭代循环和流可以很好地解决问题(示例:遍历扑克牌组合),选择哪一种这完全取决于具体情况(使用您喜欢的任何一个,您的同事是否接受您的偏好等)。第 46 项 优先使用流中没有副作用的方法。
Streams 不仅仅是一个 API,它还是一个基于函数式编程的范例。 Streams范式最重要的一点:将计算组织成一系列转换,每个阶段应该是前一阶段结果的纯函数(纯函数:无状态 -> 无副作用)。
注意:纯函数的结果仅依赖于它自己的输入,它不会依赖任何可变状态,也不会修改任何状态。例如:统计词频:forEach -> bad;收集->好。
Stream 中的 forEach 有很强的迭代味道,应该只用于报告结果,不应该用于计算,通常是一种不好的味道。代码语言:txt copy freq.keySet().stream() .sorted(comparing(freq::get).reversed()) .limit(10 ).collect(toList()); // CollectorsCollectors API的静态引入主要封装了降维操作(降维策略),其结果通常是一个集合。
注意:Scanner的stream方法是在Java 9中添加的,之前的版本可以使用streamOf(Iterable
-> 当发生冲突时会抛出异常。 toMap(keyMapper, valueMapper, mergeFunction):相同key的value将被合并。
应用1:选择值,例如:选择歌手最畅销的专辑;应用2:最终值获胜。 toMap(keyMapper,valueMapper,mergeFunction,mapSupplier):mapSupplier是一个地图工厂,用于指定具体的地图实现:EnumMap或TreeMap。
三个toMap方法都有对应的toConcurrentMap方法,创建ConcurrentHashMap实例。 groupingBy(classifier)方法用于返回按类别分组的元素,classifier(classifier函数)返回元素应该所属的类别,作为map.groupingBy(classifier,下游)的key:下游将进一步处理分类中的所有元素,可以是toSet(),toCollection(),也可以是counting()。
groupingBy(classifier,mapFactory,downstream):可以同时控制map和set的实现类型,比如TreeMap,包括TreeSet 。同样,groupingBy 方法的三个重载提供了 groupingByConcurrent 方法。
partitioningBy(predicate)返回一个Boolean类型map的key,还有一个重载的形式,添加一个下游参数。它仅用作下游方法。
比如计数,以求和、求平均、求和开头的方法,在Stream中都有对应的方法。还有reduce、filtering、mapping、flatMapping、collectingAndThen等。
minBy和maxBy是BinaryOperator中相应方法的类似。 join:共有三种重载,将字符串与特定的分隔符和前缀组合在一起。
注意:文档:Java 8 收集器。 Item 47 更喜欢使用Collection而不是Stream作为返回值 虽然Stream有符合Iterable接口规定的遍历方法,但是Stream并没有继承Interable接口。
因此,如果要遍历Stream,需要提供一个适配方法: 代码语言:txt copy // Adapter from Stream
数组有 Arrays.asList 和 Stream.of 方法。但不要将大序列放入内存中只是为了将其作为集合返回。
-> 示例:PowerSet 。 -> 自定义集合。
自定义集合可以限制元素的大小,但是如果数据太大或者数量不确定,可以考虑使用stream或者iterable。有时也会根据更直观的实现方式来选择返回类型。
示例:使用流处理生成字符串子集的集合。 PS:如果以后Stream继承了Iterable,就可以自由返回stream,因为这提供了两种可能性(流式、遍历)。
第 48 项将流变成并行要小心。如果管道的源来自 Stream.iterate,或者如果使用 limit 作为其中的方法,则使管道并行不太可能提高性能。
示例:求20个梅森素数 例如,添加parallel()方法后,程序不会输出,只能强制停止。不要不加区别地并行化流,性能影响可能是灾难性的。
数据结构并行性的性能增益在以下这些流上是最好的:ArrayList、HashMap、HashSet、ConcurrentHashMap 实例;数组;整数范围;长射程。这些数据结构的共同特征是:它们都可以准确且廉价地划分为任何所需大小的子集,这使得在并行线程之间划分工作变得容易。
它们都提供了出色的引用局部性:顺序元素引用一起存储在内存中。引用局部性对于并行批处理操作至关重要。
没有它,线程大部分时间都会处于空闲状态,等待数据从内存传输到处理器缓存。流管道的最终操作也会影响并行执行。
如果与整个管道的工作相比,最终的操作承担了很多工作,而且这个操作仍然是串行的,因此管道并行化的效果非常有限。对于并行性来说,最好的最终操作是降维操作(reductions),例如reduce、max、min、count、sum。
诸如anyMatch、allMatch 和nonMatch 之类的短路操作也适合并行性。然而,Stream(可变归约)的collect方法不太适合并行性,因为合并集合的成本很高。
如果您自己实现 Stream、Iterable 或 Collection,您想要更好的并行性能,则必须重写 spliterator 方法,并广泛测试流的并行性能。并行化流不仅会导致性能不佳,包括活性失败;它可能导致不正确的结果和不可预测的行为(安全故障)。
)。在适当的情况下,可以通过向流管道添加并行调用来实现处理器核心数量的近线性加速。
如果并行流是随机数,应该使用SplittableRandom,而不是ThreadLocalRandom(单线程使用),或者Random(每个操作都是同步的)。
版权声明:本文内容由互联网用户自发贡献,本站不拥有所有权,不承担相关法律责任。如果发现本站有涉嫌抄袭的内容,欢迎发送邮件 举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。
标签:
相关文章
06-18
06-06
06-18
06-17
最新文章
【玩转GPU】ControlNet初学者生存指南
【实战】获取小程序中用户的城市信息(附源码)
包雪雪简单介绍Vue.js:开学
Go进阶:使用Gin框架简单实现服务端渲染
线程池介绍及实际案例分享
JMeter 注释 18 - JMeter 常用配置组件介绍
基于Sentry的大数据权限解决方案
【云+社区年度征文集】GPE监控介绍及使用