正如我上周提到的, Sevilla Java 用户组 正在努力完成有关 lambda 和流的 Java 8 MOOC 。我们正在举办 三个会议 ,以便在参加该课程的人之间分享知识。
第二周的课程是关于
流的
——如何使用新的流 API 来转换数据。还有一个关于
Optional
的完整部分,最初看起来相当多,但事实证明
Optional
可以做的比我原先想象的要多。
在见面会上,我们谈到了:
Optional
:我认为我们很乐意使用
Optional
来防止
NullPointerException
。我们不太清楚的是
filter()
和
map()
的示例——如果您从流中获取
Optional
值,为什么不先在流上执行映射和过滤器?例如,为什么这样做:
list.stream()
.findFirst()
.map(String::trim)
.filter(s -> s.length() > 0)
.ifPresent(System.out::println);
什么时候可以在流中映射和过滤以获得第一个非空值?就流而言,这看起来确实是一个有趣的问题。
当其他 API 完全支持 Java 8 并返回
Optional
值时,我可以看到
Optional
更有用,然后您可以对返回值执行其他操作。
那个终端操作实际上不是终端?? :我们在会话中的示例中遇到过几次,一个示例是上面的代码(让我们将其复制到这里以便我们可以更仔细地查看它):
list.stream()
.findFirst()
.map(String::trim)
.filter(s -> s.length() > 0)
.ifPresent(System.out::println);
findFirst()
不是
终端操作
吗?你怎么能继续做更多的操作呢?
答案当然是终端操作的返回类型也可以导致进一步的操作。上面其实是:
list.stream()
.findFirst()
.map(String::trim)
.filter(s -> s.length() > 0)
.ifPresent(System.out::println);
我们的终端操作返回一个可选的,它允许你做进一步的操作。这种混淆的另一个例子:
list.stream()
.findFirst()
.map(String::trim)
.filter(s -> s.length() > 0)
.ifPresent(System.out::println);
在这里,
collect()
是一个终端操作,但它返回一个列表,它也允许
forEach()
:
list.stream()
.findFirst()
.map(String::trim)
.filter(s -> s.length() > 0)
.ifPresent(System.out::println);
所以请注意,仅仅因为它被称为终端操作,并不意味着您不能对返回值执行其他操作。
Parallel/sequential/parallel :上周有人问为什么你可以这样写代码:
list.stream()
.findFirst()
.map(String::trim)
.filter(s -> s.length() > 0)
.ifPresent(System.out::println);
以及这是否会让您决定流的哪些部分是并行的,哪些部分是串行处理的。第二课直截了当,宣布“最后的运算符获胜”——这意味着上述 所有 代码都将作为并行流运行。我找不到这方面的任何文档,如果我找到它,我会编辑这篇文章。
无序
:“为什么你会希望你的流是无序的?” - 答案是
unordered()
不会将你的排序集合变成一个没有顺序的集合
,它只是说当这段代码被执行时,元素的顺序并不重要。这可能会使并行流的处理速度更快,但作为一个团队,我们认为它在顺序流上可能毫无意义。
效率优化和流操作顺序 :我们就在流中执行操作的顺序进行了 长时间的 讨论。 MOOC(事实上,大多数关于 Streams 的文档)告诉我们 a) 流是 惰性的 ,直到遇到终端运算符才会评估 b) 这可以优化流中的操作。这引发了对以下代码的讨论:
list.stream()
.findFirst()
.map(String::trim)
.filter(s -> s.length() > 0)
.ifPresent(System.out::println);
过滤操作应该导致流中要处理的项目更少。鉴于
map()
操作不会更改
filter()
所依赖的任何内容,这段代码是否会在幕后以某种方式进行优化,以便首先实际执行过滤器?还是优化仍然会尊重流上的操作顺序?
我们的案例实际上是一个非常特殊的案例,因为 a)
map()
返回与传入的参数相同的类型(即它不将
String
映射到
int
)和 b)
map()
不改变
filter()
正在查看的特征(即长度)。但一般来说,您不能指望这些条件是真实的——事实上我敢打赌在很多情况下它们
都不是
真实的。所以流水线操作是
按照它们被写入的顺序执行的
,这意味着我们的
map
和
filter
不会被重新排序为更有效的顺序。
一个好的经验法则似乎是尽可能早地在流中进行过滤——这样您就可以潜在地减少在流的每个步骤中处理的项目数量。因此我们的代码可能会更好:
list.stream()
.findFirst()
.map(String::trim)
.filter(s -> s.length() > 0)
.ifPresent(System.out::println);
平面地图
:什么……?
flatMap()
是那些一旦你掌握了它就完全有意义的方法之一,你不明白为什么它如此令人困惑。但是你第一次遇到它时,会感到困惑——
flatMap()
与
map()
有何不同?
好吧,
flatMap
用于将(例如)流压缩成一个简单的流。这就像将二维数组转换为一维数组,这样您就可以遍历所有项目而无需嵌套 for 循环。
StackOverflow 上有一个示例
,还有更多示例可以回答
这个问题
。
比较器 :我们可能在某个时候都编写过比较器,这可能是我们“在过去”确实使用匿名内部类并期待用 lambda 替换它们的例子之一。
list.stream()
.findFirst()
.map(String::trim)
.filter(s -> s.length() > 0)
.ifPresent(System.out::println);
遗憾的是,使用 lambda 仍然不能回答“我是从 o2 中减去 o1,还是从 o1 中减去 o2?”这个问题:
list.stream()
.findFirst()
.map(String::trim)
.filter(s -> s.length() > 0)
.ifPresent(System.out::println);
但是 Java 8 中还有另一种可以拯救我们的新方法,一种几乎没有得到应有的广泛宣传的方法。有一个
Comparator.comparing()
,您可以使用它来真正轻松地定义要比较的内容。 JavaDoc 和签名看起来有点混乱,但这是方法引用突然变得有意义的地方之一:
list.stream()
.findFirst()
.map(String::trim)
.filter(s -> s.length() > 0)
.ifPresent(System.out::println);
(这里我们实际上使用了
comparingInt
方法,因为我们要比较原始值)。就个人而言,这是我最喜欢的 Java 8 新特性之一。
下周加入我们,参加
关于 Java 8 的最后一场会议 - Lambdas 和 Streams
。