ThinkSSL🔒 一键申购 5分钟快速签发 30天无理由退款 购买更放心 广告
### [在`map()`中组合流](https://lingcoder.gitee.io/onjava8/#/book/14-Streams?id=%e5%9c%a8-map-%e4%b8%ad%e7%bb%84%e5%90%88%e6%b5%81) 假设我们现在有了一个传入的元素流,并且打算对流元素使用`map()`函数。现在你已经找到了一些可爱并独一无二的函数功能,但是问题来了:这个函数功能是产生一个流。我们想要产生一个元素流,而实际却产生了一个元素流的流。 `flatMap()`做了两件事:将产生流的函数应用在每个元素上(与`map()`所做的相同),然后将每个流都扁平化为元素,因而最终产生的仅仅是元素。 `flatMap(Function)`:当`Function`产生流时使用。 `flatMapToInt(Function)`:当`Function`产生`IntStream`时使用。 `flatMapToLong(Function)`:当`Function`产生`LongStream`时使用。 `flatMapToDouble(Function)`:当`Function`产生`DoubleStream`时使用。 为了弄清它的工作原理,我们从传入一个刻意设计的函数给`map()`开始。该函数接受一个整数并产生一个字符串流: ~~~ // streams/StreamOfStreams.java import java.util.stream.*; public class StreamOfStreams { public static void main(String[] args) { Stream.of(1, 2, 3) .map(i -> Stream.of("Gonzo", "Kermit", "Beaker")) .map(e-> e.getClass().getName()) .forEach(System.out::println); } } ~~~ 输出结果: ~~~ java.util.stream.ReferencePipeline$Head java.util.stream.ReferencePipeline$Head java.util.stream.ReferencePipeline$Head ~~~ 我们天真地希望能够得到字符串流,但实际得到的却是“Head”流的流。我们可以使用`flatMap()`解决这个问题: ~~~ // streams/FlatMap.java import java.util.stream.*; public class FlatMap { public static void main(String[] args) { Stream.of(1, 2, 3) .flatMap(i -> Stream.of("Gonzo", "Fozzie", "Beaker")) .forEach(System.out::println); } } ~~~ 输出结果: ~~~ Gonzo Fozzie Beaker Gonzo Fozzie Beaker Gonzo Fozzie Beaker ~~~ 从映射返回的每个流都会自动扁平为组成它的字符串。 下面是另一个演示,我们从一个整数流开始,然后使用每一个整数去创建更多的随机数。 ~~~ // streams/StreamOfRandoms.java import java.util.*; import java.util.stream.*; public class StreamOfRandoms { static Random rand = new Random(47); public static void main(String[] args) { Stream.of(1, 2, 3, 4, 5) .flatMapToInt(i -> IntStream.concat( rand.ints(0, 100).limit(i), IntStream.of(-1))) .forEach(n -> System.out.format("%d ", n)); } } ~~~ 输出结果: ~~~ 58 -1 55 93 -1 61 61 29 -1 68 0 22 7 -1 88 28 51 89 9 -1 ~~~ 在这里我们引入了`concat()`,它以参数顺序组合两个流。 如此,我们在每个随机`Integer`流的末尾添加一个 -1 作为标记。你可以看到最终流确实是从一组扁平流中创建的。 因为`rand.ints()`产生的是一个`IntStream`,所以我必须使用`flatMap()`、`concat()`和`of()`的特定整数形式。 让我们再看一下将文件划分为单词流的任务。我们最后使用到的是**FileToWordsRegexp.java**,它的问题是需要将整个文件读入行列表中 —— 显然需要存储该列表。而我们真正想要的是创建一个不需要中间存储层的单词流。 下面,我们再使用 `flatMap()`来解决这个问题: ~~~ // streams/FileToWords.java import java.nio.file.*; import java.util.stream.*; import java.util.regex.Pattern; public class FileToWords { public static Stream<String> stream(String filePath) throws Exception { return Files.lines(Paths.get(filePath)) .skip(1) // First (comment) line .flatMap(line -> Pattern.compile("\\W+").splitAsStream(line)); } } ~~~ `stream()`现在是一个静态方法,因为它可以自己完成整个流创建过程。 注意:`\\W+`是一个正则表达式。表示“非单词字符”,`+`表示“可以出现一次或者多次”。小写形式的`\\w`表示“单词字符”。 我们之前遇到的问题是`Pattern.compile().splitAsStream()`产生的结果为流,这意味着当我们只是想要一个简单的单词流时,在传入的行流(stream of lines)上调用`map()`会产生一个单词流的流。幸运的是,`flatMap()`可以将元素流的流扁平化为一个简单的元素流。或者,我们可以使用`String.split()`生成一个数组,其可以被`Arrays.stream()`转化成为流: ~~~ .flatMap(line -> Arrays.stream(line.split("\\W+")))) ~~~ 因为有了真正的流(而不是`FileToWordsRegexp.java`中基于集合存储的流),所以每次需要一个新的流时,我们都必须从头开始创建,因为流不能被复用: ~~~ // streams/FileToWordsTest.java import java.util.stream.*; public class FileToWordsTest { public static void main(String[] args) throws Exception { FileToWords.stream("Cheese.dat") .limit(7) .forEach(s -> System.out.format("%s ", s)); System.out.println(); FileToWords.stream("Cheese.dat") .skip(7) .limit(2) .forEach(s -> System.out.format("%s ", s)); } } ~~~ 输出结果: ~~~ Not much of a cheese shop really is it ~~~ 在`System.out.format()`中的`%s`表明参数为**String**类型。