From 337da691b2072c63f46de46eaac54d12ca6d3ef9 Mon Sep 17 00:00:00 2001 From: sjsdfg <736777445@qq.com> Date: Sat, 20 Apr 2019 17:13:50 +0800 Subject: [PATCH 1/2] =?UTF-8?q?14=E7=AB=A0=EF=BC=9A=E6=B5=81=E4=B8=AD?= =?UTF-8?q?=E9=97=B4=E6=93=8D=E4=BD=9C=E9=83=A8=E5=88=86=E5=B0=8F=E8=8A=82?= =?UTF-8?q?=E7=BF=BB=E8=AF=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/14-Streams.md | 403 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 403 insertions(+) diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index e6dbd49..56f745c 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -729,8 +729,411 @@ Not much of a cheese shop really is it ## 中间操作 +中间操作用于在一个流中获取元素,并将元素放入另一个流的后端用以连接不同的操作。 + +### 跟踪和调试 + +`peek()` 操作的目的是帮助调试。它允许你无修改的查看流中的元素: + +```java +// streams/Peeking.java +class Peeking { + public static void main(String[] args) throws Exception { + FileToWords.stream("Cheese.dat") + .skip(21) + .limit(4) + .map(w -> w + " ") + .peek(System.out::print) + .map(String::toUpperCase) + .peek(System.out::print) + .map(String::toLowerCase) + .forEach(System.out::print); + } +} +``` + +输出为: + +```java +Well WELL well it IT it s S s so SO so +``` + +**FileToWords** 很快就被定义好了,但是它的功能就像我们之前所看到的的版本那样:产生 **String** 对象的流。之后在他们通过管道的时候使用 `peek()` 偷窥它们。 + +因为 `peek()` 符合 没有返回值的 **Consumer** 函数式接口,所以不可能使用不同的元素来替换流中的对象。你只能观察它们。 + +### 排序流中元素 + +你已经在 **Randoms.java** 看到了使用默认比较器的 `sorted()` 函数。还有 `sorted()` 的第二种形式,需要传入一个 **Comparator** 参数: + +```java +// streams/SortedComparator.java +import java.util.*; +public class SortedComparator { + public static void main(String[] args) throws Exception { + FileToWords.stream("Cheese.dat") + .skip(10) + .limit(10) + .sorted(Comparator.reverseOrder()) + .map(w -> w + " ") + .forEach(System.out::print); + } +} +``` + +输出为: + +```java +you what to the that sir leads in district And +``` + +你可以为 `sorted()` 传入一个 lambda 函数作为其参数,但是这里也有预先实现好的比较器 —— 在这里我们所使用的是反转“自然顺序”。 + +### 移除元素 + +`distinct()`:在 **Randoms.java** 中,`distinct()` 去除了流中的重复元素。使用 `distinct()` 相比创建一个 `Set` 用于消除重复元素的工作量要小得多。 + +`filter(Predicate)`:过滤操作只会保留那些传递给参数是产生 true 的元素 - 过滤器函数。 + +在这个例子中,过滤器函数是 ` isPrime()`,用于检测质数。 + +```java +// streams/Prime.java +import java.util.stream.*; +import static java.util.stream.LongStream.*; +public class Prime { + public static Boolean isPrime(long n) { + return rangeClosed(2, (long)Math.sqrt(n)) + .noneMatch(i -> n % i == 0); + } + public LongStream numbers() { + return iterate(2, i -> i + 1) + .filter(Prime::isPrime); + } + public static void main(String[] args) { + new Prime().numbers() + .limit(10) + .forEach(n -> System.out.format("%d ", n)); + System.out.println(); + new Prime().numbers() + .skip(90) + .limit(10) + .forEach(n -> System.out.format("%d ", n)); + } +} +``` + +输出为: + +```java +2 3 5 7 11 13 17 19 23 29 +467 479 487 491 499 503 509 521 523 541 +``` + +`rangeClosed() ` 包含了上界值。如果余数没有产生 0,则 `noneMatch()` 操作返回 ture,如果出现任何等于 0 的则返回 false。 `noneMatch()` 操作在第一次失败之后就会推出,而不是进行全部尝试。 + +### 应用操作到所有元素 + +`map(Function) `:将 **Function** 操作应用在输入流的每一个元素中,并将返回值传递到输出流中。 + +`mapToInt(ToIntFunction)`:操作同上,但结果是 **IntStream**。 + +`mapToLong(ToLongFunction)`:操作同上,但结果是 **LongStream**。 + +`mapToDouble(ToDoubleFunction)` : 操作同上,但结果是 **DoubleStream**。 + +在这里,我们使用 `map()` 映射多种函数到一个字符串流中: + +```java +// streams/FunctionMap.java +import java.util.*; +import java.util.stream.*; +import java.util.function.*; +class FunctionMap { + static String[] elements = { "12", "", "23", "45" }; + static Stream + testStream() { + return Arrays.stream(elements); + } + static void test(String descr, Function func) { + System.out.println(" ---( " + descr + " )---"); + testStream() + .map(func) + .forEach(System.out::println); + } + public static void main(String[] args) { + test("add brackets", s -> "[" + s + "]"); + test("Increment", s -> { + try { + return Integer.parseint(s) + 1 + ""; + } + catch(NumberFormatException e) { + return s; + } + } + ); + test("Replace", s -> s.replace("2", "9")); + test("Take last digit", s -> s.length() > 0 ? + s.charAt(s.length() - 1) + "" : s); + } +} +``` + +输出为: + +```java +---( add brackets )--- +[12] +[] +[23] +[45] +---( Increment )--- +13 +24 +46 +---( Replace )--- +19 +93 +45 +---( Take last digit )--- +2 +3 +5 +``` + +在“Increment”测试中,我们使用 `Integer.parseInt()` 去试图将一个字符串转化为整数。如果字符串不能转化成为整数就会抛出一个 **NumberFormatException** 异常,我们只需回过头来将原始字符串放回到输出流中。 + +在以上例子中,` map()` 将一个字符串映射为另一个字符串,但是我们完全可以产生和接收类型完全不同的类型,从而改变流的数据类型。这里是一个例子: + +```java +// streams/FunctionMap2.java +// Different input and output types +import java.util.*; +import java.util.stream.*; +class Numbered { + final int n; + Numbered(int n) { + this.n = n; + } + @Override + public String toString() { + return "Numbered(" + n + ")"; + } +} +class FunctionMap2 { + public static void main(String[] args) { + Stream.of(1, 5, 7, 9, 11, 13) + .map(Numbered::new) + .forEach(System.out::println); + } +} +``` + +输出为: + +```java +Numbered(1) +Numbered(5) +Numbered(7) +Numbered(9) +Numbered(11) +Numbered(13) +``` + +我们获取了许多 int 类型整数,并通过构造器 `Numbered::new` 将它们转化成为 **Numbereds** 类型。 + +如果使用 **Function** 产生的结果是数值类型的一种,你必须使用相似的 **mapTo**-operations 操作进行替代: + +```java +// streams/FunctionMap3.java +// Producing numeric output streams +import java.util.*; +import java.util.stream.*; +class FunctionMap3 { + public static void main(String[] args) { + Stream.of("5", "7", "9") + .mapToint(Integer::parseint) + .forEach(n -> System.out.format("%d ", n)); + System.out.println(); + Stream.of("17", "19", "23") + .mapTolong(long::parselong) + .forEach(n -> System.out.format("%d ", n)); + System.out.println(); + Stream.of("17", "1.9", ".23") + .mapTodouble(double::parsedouble) + .forEach(n -> System.out.format("%f ", n)); + } +} +``` + +输出为: + +```java +5 7 9 +17 19 23 +17.000000 1.900000 0.230000 +``` + +不幸的是,Java 设计者并没有尽最大的努力来消除原始类型。 + +### 在 map() 期间组合流 + +假如你有一个即将到来的元素流,并且你打算对流元素使用 `map()` 函数。你已经找到了那些你在其他地方找不到的可爱的函数功能,但是这里有一个问题:这个函数功能产生一个流。你想要的只是产生一个元素流,但是你生成的是一个元素流的流。 + +`flatMap()` 做了两件事情:它获取你的流产生( stream-producing)函数,并将其应用于新到的元素(正如 `map()` 所做的),然后获取每一个流并将其“展平”为元素。所以它的输出只是元素。 + +`flatMap(Function)`:当 **Function** 产生流时使用。 + +`flatMapToInt(Function)`:当 **Function** 产生 **IntStream** 时使用。 + +`flatMapToLong(Function)`:当 **Function** 产生 **LongStream** 时使用。 + +`flatMapToDouble(Function)`:当 **Function** 产生 **DoubleStream** 时使用。 + +为了了解它是如何工作的,我们将从 `map()` 的一个刻意设计的函数开始,这个函数接受一个整数并产生一个字符串流: + +```java +// 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 +java.util.stream.ReferencePipeline$Head +java.util.stream.ReferencePipeline$Head +java.util.stream.ReferencePipeline$Head +``` + +我们天真的希望能够得到字符串流,但是我们得到的确实流元素为“Head”流的流。但是我们可以使用 `flatMap()` 来轻松解决这个问题: + +```java +// 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); + } +} +``` + +输出为: + +```java +Gonzo +Fozzie +Beaker +Gonzo +Fozzie +Beaker +Gonzo +Fozzie +Beaker +``` + +因此,从映射返回的每个流都会自动展平为其组件字符串。 + +如下是另一演示,我们从一个整数流开始,然后使用每一个整数去创建许多随机数。 + +```java +// 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)); + } +} +``` + +输出为: + +```java +58 -1 55 93 -1 61 61 29 -1 68 0 22 7 -1 88 28 51 89 9 -1 +``` + +我在这里引入了`concat()`,它以参数顺序组合了两个流。 因此,在每个随机 Integer 流的末尾,我添加一个 -1 作为标记,因此ni你可以看到最终流确实是从一组展平流中创建的。 + +因为 `rand.ints()` 产生了一个 **IntStream**,所以我必须使用 `flatMap()`、`concat()` 和 `of()` 的特定整数版本。 + +让我们再看一下将文件划分为单词流的任务。我们上一次遇到的是 **FileToWordsRegexp.java**,它的问题是它需要我们将整个文件读入行列表中 —— 因此我们需要存储该列表。我们真正想要的是创建以一个不需要中间存储的单词流。 + +再一次,我们使用 ` flatMap()` 来解决这个问题: + +```java +// streams/FileToWords.java +import java.nio.file.*; +import java.util.stream.*; +import java.util.regex.Pattern; +public class FileToWords { + public static Stream 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** 的意思是 “非单词字符”,**+** 的意思是“可以出现一次或者多次”。小写版本的 “**\\\\w**” 代表“单词字符”。 + +我们之前遇到的问题是 `Pattern.compile().splitAsStream()`产生的结果为流,这意味着当我们只是想要一个简单的单词流,在传入的行流(stream of lines)上调用 `map()` 会产生一个单词流的流。幸运的是,`flatMap()` 可以将元素流的流展平为一个简单的元素流。或者,我们可以使用 `String.split()` 生成一个数组,其可以被 `Arrays.stream()` 转化成为流: + +```java +.flatMap(line -> Arrays.stream(line.split("\\W+")))) +``` + +因为我们拥有了一个真的流(而不是在 **FileToWordsRegexp.java** 基于集合存储的流),每一次我们想要一个新的流就必须从头创建,因为这个流并不能被重复使用: + +```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)); + } +} +``` + +输出为: + +```java +Not much of a cheese shop really +``` + +在 `System.out.format()` 中的 **%s** 表名参数为 String 类型。 + +### Optional + + + + + ## Optional类 From 252f05eb94bf3470081ee8327a7983360350b717 Mon Sep 17 00:00:00 2001 From: sjsdfg <736777445@qq.com> Date: Sat, 20 Apr 2019 17:16:51 +0800 Subject: [PATCH 2/2] no message --- docs/book/14-Streams.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/book/14-Streams.md b/docs/book/14-Streams.md index 56f745c..036548f 100644 --- a/docs/book/14-Streams.md +++ b/docs/book/14-Streams.md @@ -1126,12 +1126,8 @@ Not much of a cheese shop really 在 `System.out.format()` 中的 **%s** 表名参数为 String 类型。 -### Optional - - - ## Optional类