我知道我们无法实例化界面,但在从Streams
的书中做教程时,我感到困惑。
我将使用代码部分来突出显示我不理解的部分。
// count occurences of each word in a Stream<String> sorted by word
Map<String, Long> wordCounts =
Files.lines(Paths.get("Chapter2Paragraph.txt"))
.map(line -> line.replaceAll("(?!')\\p{P}", ""))
.flatMap(line -> pattern.splitAsStream(line))
.collect(Collectors.groupingBy(String::toLowerCase,
TreeMap::new, Collectors.counting()));
对于方法flatMap
,当在API中单击时,它会说:
返回一个流,该流包含将此流的每个元素替换为通过将提供的映射函数应用于每个元素而生成的映射流的内容的结果。在将其内容放入此流后,每个映射的流都将关闭。 (如果映射的流为空,则使用空流,而不是。)
那是什么意思呢?我有点理解它的作用,但我根本不明白它是如何在幕后工作的。在这种情况下API提及返回时是否返回一个Object,或者它是否意味着它替换了当前流?另外,在使用Streams
时,编译器是否实际创建了这些元素的Object,然后在完成后终止?
此外,从上面的代码中,我只想确保我是正确的。
当你有一个Map<String, Long> wordCounts
变量时,是否意味着在流终止结束时,最终结果必须完全遵循类型推断?
答案 0 :(得分:2)
flapMap()
将每个元素转换为流(任何类型)。这些流连接在一起形成一个大流。
在您的示例中,在模式上分割每一行(未在问题中指定)之后,整个文件将流式传输(作为一个流)。
答案 1 :(得分:1)
您似乎已经大致正确地分析了这种情况,但也许您对某些中间步骤感到朦胧。
您有一系列方法,这些方法在链中前一个方法的返回值上调用。最终方法(collect
)的返回值存储在名为Map
的{{1}}中。无论方法究竟做什么以及返回什么(只是暂时),当你调用这样的方法链时,这是标准的行为。
地图的泛型类型由最终方法调用中的wordCounts
和String::toLowerCase
确定。前者指定密钥类型为Collectors.counting()
,后者指定值为String
。例如,如果您使用Long
作为关键字,那么您将获得类型为String::length
的地图,这将计算出具有给定长度的字词的出现次数。< / p>
回到函数调用序列,它可以按如下方式分解:
Files.lines(Path)
在文件中创建Stream<String>
行。由于结果是流,您现在可以调用... Stream.map(Function<String, String>)
使用对Map<Integer, Long>
的调用将输入的字符串流转换为另一个字符串流。已编辑的行流现在获得Stream.flatMap(Function<String, Stream<String>>)
以将行拆分为单词并返回单个连续流。请记住,line.replaceAll(...)
将按顺序应用于每一行,因此将返回与行数一样多的流。 pattern.splitAsStream
获取所有这些流并将它们串联成一个连续的流。
请注意,封装的整个目的是您无需确切了解流程的工作原理。您只需知道最终结果是什么(在本例中为Stream.flatMap
)。您应该能够将预先读取所有流的实现交换到底层集合中,并从中返回一个流,并在处理元素时懒惰地打开每个流,而不必担心实际发生的情况。
Stream<String>
个单词,您可以应用所谓的终端操作:Stream<String>
。收集器由Collectors.groupingBy(Function<String, String>, Supplier<Map<String, String>>, Collector<String, String, Long>)
创建。这将创建一个收集器,根据分类器Stream.collect(Collector<String, String, Map<String, Long>>)
(Function
)返回的密钥将输入流分组为子流,并将其传递到“下游”收集器以对每个子进行实际累积-流。生成的累积存储在String.toLowerCase()
(Supplier
)返回的地图中。下游TreeMap::new
由Collectors.counting()
创建,它只计算每个流中元素的数量。我已经扩展了此描述中的所有泛型类型,以便更容易跟进并查看每个步骤产生的对象类型。
更一般地说,Java中的流有两种类型的操作:中间和终端。流来自源(在本例中为您的文件)。所有中间操作(1-3)将一个流转换为另一个流。输入和输出类型总是清晰定义,如上所示,就像任何其他操作一样。终端操作是基于流返回某种值的单个操作的操作。在您的情况下,您计算单词频率并将它们存入Collector
。这在java.util.stream
package summary中已有很好的记录。