[ISSUE #40]校订流式编程 close #40

This commit is contained in:
LingCoder
2020-01-11 17:57:08 +08:00
parent 2913b9bc6c
commit 30986939c3
3 changed files with 35 additions and 33 deletions

View File

@@ -646,7 +646,7 @@ public class FunctionalAnnotation {
4. 如果返回值类型与参数类型一致,则是一个运算符:单个参数使用 `UnaryOperator`,两个参数使用 `BinaryOperator`
5. 如果接收两个参数且返回值为布尔值,则是一个谓词Predicate
5. 如果接收两个参数且返回值为布尔值,则是一个断言Predicate
6. 如果接收的两个参数类型不同,则名称中有一个 `Bi`

View File

@@ -1683,15 +1683,15 @@ Signal(dash)
## 终端操作
这些操作接收一个流并产生一个最终结果;它们不会向后端流提供任何东西。因此,终端操作总是你在管道中做的最后一件事
以下操作将会获取流的最终结果。至此我们无法再继续往后传递流。可以说,终端操作总是我们在流管道中做的最后一件事。
<!-- Convert to an Array -->
### 转化为数组
### 数组
- `toArray()`:将流转换成适当类型的数组。
- `toArray(generator)`:在特殊情况下,生成器用于分配自定义的数组存储
- `toArray(generator)`:在特殊情况下,生成自定义类型的数组。
这组方法在流操作产生的结果必须是数组形式时很有用。假我们想在流里复用获取的随机数,可以将他们保存到数组中。代码示例:
当我们需要得到数组类型的数据以便于后续操作时,上面的方法就很有用。假我们需要复用流产生的随机数时,就可以这么使用。代码示例:
```java
// streams/RandInts.java
@@ -1706,19 +1706,19 @@ public class RandInts {
}
```
上例将100个数值范围在 0 到 1000 之间的随机数流转换成为数组并将其存储在 `rints` 中,每次调用 `rands()` 的时候可以重复获取相同的流。
上例将100个数值范围在 0 到 1000 之间的随机数流转换成为数组并将其存储在 `rints`。这样一来,每次调用 `rands()` 的时候可以重复获取相同的整数流。
<!-- Apply a Final Operation to Every Element -->
### 应用最终操作
### 循环
- `forEach(Consumer)`:你已经看到过很多次 `System.out::println` 作为 **Consumer** 函数。
- `forEach(Consumer)`常见如 `System.out::println` 作为 **Consumer** 函数。
- `forEachOrdered(Consumer)` 保证 `forEach` 按照原始流顺序操作。
第一种形式:显式设计为任意顺序操作元素,仅在引入 `parallel()` 操作时才有意义。在 [并发编程](24-Concurrent-Programming.md) 章节之前我们不会深入研究这个问题。这里简单介绍下 `parallel()`:可实现多处理器并行操作。实现原理为将流分割为多个(通常数目为 CPU 核心数)并在不同处理器上分别执行操作。因为我们采用的是内部迭代,而不是外部迭代,所以这是可能实现的。
第一种形式:序操作,仅在引入并行流时才有意义。在 [并发编程](24-Concurrent-Programming.md) 章节之前我们不会深入研究这个问题。这里简单介绍下 `parallel()`:可实现多处理器并行操作。实现原理为将流分割为多个(通常数目为 CPU 核心数)并在不同处理器上分别执行操作。因为我们采用的是内部迭代,而不是外部迭代,所以这是可能实现的。
`parallel()` 看似简单,实则棘手。更多内容将在稍后的 [并发编程](24-Concurrent-Programming.md) 章节中学习。
下例引入 `parallel()` 来帮助理解 `forEachOrdered(Consumer)` 的作用和使用场景。代码示例:
下例引入 `parallel()` 来帮助理解 `forEachOrdered(Consumer)` 的作用和使用场景。代码示例:
```java
// streams/ForEach.java
@@ -1756,7 +1756,7 @@ public class ForEach {
<!-- Collecting -->
###
### 集
- `collect(Collector)`:使用 **Collector** 收集流元素到结果集合中。
- `collect(Supplier, BiConsumer, BiConsumer)`:同上,第一个参数 **Supplier** 创建了一个新结果集合,第二个参数 **BiConsumer** 将下一个元素包含到结果中,第三个参数 **BiConsumer** 用于将两个值组合起来。
@@ -1798,7 +1798,7 @@ stream, streams, throws, toCollection, trim, util,
void, words2]
```
**Files.**`lines()` 打开 **Path** 并将其转换成为行流。下一行代码将匹配一个或多个非单词字符(`\\w+`)行进行分割,然后使用 **Arrays.**`stream()` 将其转化成为流,并将结果平映射成为单词流。使用 `matches(\\d+)` 查找并移除全数字字符串(**注意**,`words2` 是通过的)。接下来我们使用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于3的单词紧接着只获取100个单词最后将其保存到 **TreeSet** 中。
**Files.**`lines()` 打开 **Path** 并将其转换成为行流。下一行代码将匹配一个或多个非单词字符(`\\w+`)行进行分割,然后使用 **Arrays.**`stream()` 将其转化成为流,并将结果平映射成为单词流。使用 `matches(\\d+)` 查找并移除全数字字符串(**注意**,`words2` 是通过的)。接下来我们使用 **String.**`trim()` 去除单词两边的空白,`filter()` 过滤所有长度小于3的单词紧接着只获取100个单词最后将其保存到 **TreeSet** 中。
<!--
Files.lines() opens the Path and turns it into a Stream oflines. The next line splits those lines on boundaries of one or morenon-word characters (\\W+), which produces an array which is turnedinto a Stream with Arrays.stream(), and the result is flat-mapped back into a Stream of words. The matches(\\d+) finds
@@ -1858,7 +1858,7 @@ public class MapCollector {
在这里,我们只使用最简单形式的 `Collectors.toMap()`,这个方法值需要一个可以从流中获取键值对的函数。还有其他重载形式,其中一种形式是在遇到键值冲突时,需要一个函数来处理这种情况。
大多数情况下,你可以在 `java.util.stream.Collectors`寻找到你想要的预先定义好**Collector**。在少数情况下当你找不到想要的时候,你可以使用第二种形式的 ` collect()`。 我基本上把它留作更高级的练习,但是这里有一个例子给出基本法:
大多数情况下,`java.util.stream.Collectors` 中预设**Collector** 就能满足我们的要求。除此之外,你可以使用第二种形式的 `collect()`。 我把它留作更高级的练习,下例给出基本法:
<!--
The capChars randomly-generated Iterator of capitalletters starts as a stream, then the iterator() method allows us touse it in the stream() method
@@ -1893,13 +1893,13 @@ cheese
<!-- Combining All Stream Elements -->
### 组合所有流元素
### 组合
- `reduce(BinaryOperator)`:使用 **BinaryOperator** 来组合所有流中的元素。因为流可能为空,其返回值为 **Optional**
- `reduce(identity, BinaryOperator)`:功能同上,但是使用 **identity** 作为其组合的初始值。因此如果流为空,**identity** 就是结果。
- `reduce(identity, BiFunction, BinaryOperator)`这个形式更为复杂(所以我们不会介绍这里被提到是因为它使用起来会更有效。通常,可以显式地组合 `map()``reduce()` 来更简单的表达它。
- `reduce(identity, BiFunction, BinaryOperator)`更复杂的使用形式(暂不介绍),这里把它包含在内,因为它可以提高效率。通常,我们可以显式地组合 `map()``reduce()` 来更简单的表达它。
如下是一个用于演示 `reduce()` 的示例:
下面来看下 `reduce`代码示例:
```java
// streams/Reduce.java
@@ -1946,20 +1946,20 @@ Frobnitz(7)
Frobnitz(29)
```
**Frobnitz** 包含了一个名为 `supply()` 的生成器;因为这个方法对于 `Supplier<Frobnitz>` 是签名兼容的,我们可以将其方法引用传递给 `Stream.generate()`(这种签名兼容性被称作结构一致性)。我们使用没有给“起始值”的 `reduce()`方法,这意味着它的返回值是 **Optional** 类型`Optional.ifPresent()` 只有在结果非空的时候才会调用 `Consumer<Frobnitz>` `println` 方法可以被调用是因为 **Frobnitz** 可以通过 `toString()` 方法转换成 **String**)。
**Frobnitz** 包含了一个名为 `supply()` 的生成器;因为这个方法对于 `Supplier<Frobnitz>` 是签名兼容的,我们可以将其方法引用传递给 `Stream.generate()`(这种签名兼容性被称作结构一致性)。无“初始值”的 `reduce()`方法返回值是 **Optional** 类型。`Optional.ifPresent()` 只有在结果非空的时候才会调用 `Consumer<Frobnitz>` `println` 方法可以被调用是因为 **Frobnitz** 可以通过 `toString()` 方法转换成 **String**)。
Lambda 表达式中的第一个参数 `fr0` 是上一次调用 `reduce()` 的结果。而第二个参数 `fr1` 是从流传递过来的值。
`reduce()` 中的 Lambda 表达式使用了三元表达式来获取结果,当其 size 小于 50 的时候获取 `fr0` 否则获取序列中的下一个值 `fr1`因此你会取得第一个 size 小于 50 的 `Frobnitz`,只要找到了就这个结果就会紧紧地攥住它,即使有其他候选者出现。虽然这是个非常奇怪的约束,但是它确实让`reduce()` 有了更多的了解。
`reduce()` 中的 Lambda 表达式使用了三元表达式来获取结果,当其长度小于 50 的时候获取 `fr0` 否则获取序列中的下一个值 `fr1`取得第一个长度小于 50 的 `Frobnitz`,只要得到结果就会忽略其他。这是个非常奇怪的约束,确实让我们`reduce()` 有了更多的了解。
<!-- Matching -->
### 匹配
- `allMatch(Predicate)` :如果流的每个元素根据提供的 **Predicate** 都返回 true 时,结果返回为 true。这个操作将会在第一个 false 之后短路;也就是不会在发生 false 之后继续执行计算。
- `anyMatch(Predicate)`:如果流中的任意一个元素根据提供的 **Predicate** 返回 true 时,结果返回为 true。这个操作将会在第一个 true 之后短路;也就是不会在发生 true 之后继续执行计算。
- `noneMatch(Predicate)`:如果流的每个元素根据提供的 **Predicate** 都返回 false 时,结果返回为 true。这个操作将会在第一个 true 之后短路;也就是不会在发生 true 之后继续执行计算。
- `allMatch(Predicate)` :如果流的每个元素根据提供的 **Predicate** 都返回 true 时,结果返回为 true。在第一个 false 时,则停止执行计算。
- `anyMatch(Predicate)`:如果流中的任意一个元素根据提供的 **Predicate** 返回 true 时,结果返回为 true。在第一个 false 是停止执行计算。
- `noneMatch(Predicate)`:如果流的每个元素根据提供的 **Predicate** 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。
已经在 `Prime.java` 中看到了 `noneMatch()` 的示例;` allMatch()``anyMatch()` 的用法基本上是等同的。我们探究短路行为。为了创建消除冗余代码 ` show()` 方法,我们必须首先发现如何统一地描述所有三个匹配器操作,然后将其转换为称作 **Matcher** 接口:
我们已经在 `Prime.java` 中看到了 `noneMatch()` 的示例;`allMatch()``anyMatch()` 的用法基本上是等同的。下面我们探究一下短路行为。为了消除冗余代码,我们创建了 `show()`。首先我们必须治到如何统一地描述三个匹配器操作,然后将其转换为 **Matcher** 接口。代码示例
```java
// streams/Matching.java
@@ -2001,15 +2001,17 @@ public class Matching {
1 2 3 4 5 6 7 8 9 true
```
**BiPredicate** 是一个二元谓词,这意味着它只能接受两个参数且只返回 true 或者 false。它的第一个参数是我们要测试的流第二个参数是一个谓词 **Predicate**因为 **Matcher** 适用于所有的 **Stream::*Match** 方法形式,所以我们可以传递每一个到 `show()` 中。`match.test()` 的调用会被转换成 **Stream::*Match** 函数的调用。
**BiPredicate** 是一个二元断言,它只能接受两个参数且只返回 true 或者 false。它的第一个参数是我们要测试的流第二个参数是一个断言 **Predicate**。**Matcher** 适用于所有的 **Stream::\*Match** 方法,所以我们可以传递每一个到 `show()` 中。`match.test()` 的调用会被转换成 **Stream::\*Match** 函数的调用。
`show()` 获取两个参数,**Matcher** 匹配器和用于表示谓词测试 **n < val** 中最大值的 **val**。这个方法生成一个从 1 到 9 的整数流。`peek()` 是用于向我们展示测试在短路之前的情况。你可以在输出中发现每一次短路都会发生
`show()` 获取两个参数,**Matcher** 匹配器和用于表示断言测试 **n < val** 中最大值的 **val**。这个方法生成一个1-9之间的整数流。`peek()` 是用于向我们展示测试在短路之前的情况。从输出中可以看到每次都发生了短路
### 元素查找
### 查找
- `findFirst()`:返回一个含有第一个流元素的 **Optional**,如果流为空返回 **Optional.empty**
- `findFirst()`:返回第一个流元素的 **Optional**,如果流为空返回 **Optional.empty**
- `findAny(`:返回含有任意流元素的 **Optional**,如果流为空返回 **Optional.empty**
代码示例:
```java
// streams/SelectElement.java
import java.util.*;
@@ -2038,7 +2040,7 @@ public class SelectElement {
`findFirst()` 无论流是否为并行化的,总是会选择流中的第一个元素。对于非并行流,`findAny()`会选择流中的第一个元素(即使从定义上来看是选择任意元素)。在这个例子中,我们使用 `parallel()` 来并行流从而引入 `findAny()` 选择非第一个流元素的可能性。
如果必须选择流中最后一个元素,那就使用 ` reduce()`
如果必须选择流中最后一个元素,那就使用 `reduce()`。代码示例
```java
// streams/LastElement.java
@@ -2076,7 +2078,7 @@ three
- `max(Comparator)`:根据所传入的 **Comparator** 所决定的“最大”元素。
- `min(Comparator)`:根据所传入的 **Comparator** 所决定的“最小”元素。
字符串类型有预先定义好**Comparator**,这简化了我们的示例:
**String** 类型有预设**Comparator** 实现。代码示例:
```java
// streams/Informational.java
@@ -2113,9 +2115,9 @@ you
### 数字流信息
- `average()` :求取流元素平均值。
- `max()``min()`因为这些操作在数字流上面,所以不需要 **Comparator**
- `max()``min()`数值流操作无需 **Comparator**
- `sum()`:对所有流元素进行求和。
- `summaryStatistics()`:生成可能有用的数据。目前不太清楚他们为什么觉得有必要这样做,因为你可以使用直接的方法产生所有的数据。
- `summaryStatistics()`:生成可能有用的数据。目前不太清楚这个方法存在的必要性,因为我们其实可以用更直接的方法获得需要的数据。
```java
// streams/NumericStreamInfo.java
@@ -2142,11 +2144,11 @@ public class NumericStreamInfo {
IntSummaryStatistics{count=100, sum=50794, min=8, average=507.940000, max=998}
```
这些操作对于 **LongStream****DoubleStream** 同样适用。
上例操作对于 **LongStream****DoubleStream** 同样适用。
## 本章小结
流改变并极大地提升了 Java 编程的性质,并可能极大地阻止了 Java 编程人员向诸如 Scala 这种函数式语言的流。在本书的剩余部分,我们将尽可能地使用流。
式操作改变并极大地提升了 Java 语言的可编程性,并可能极大地阻止了 Java 编程人员向诸如 Scala 这种函数式语言的流。在本书的剩余部分,我们将尽可能地使用流。
<!-- 分页 -->

View File

@@ -1613,7 +1613,7 @@ Brasilia
| **Iterator\<T\> iterator() Spliterator\<T\> spliterator()** | 返回一个迭代器来遍历集合中的元素。 **Spliterators** 更复杂一些,它用在并发场景 |
| **boolean remove(Object)** | 如果目标集合包含该参数,则在集合中删除该参数,如果成功删除则返回 **true** 。(“可选的”) |
| **boolean removeAll(Collection\<?\>)** | 删除目标集合中,参数集合所包含的全部元素。如果有元素被成功删除则返回 **true** 。 (“可选的”) |
| **boolean removeIf(Predicate\<? super E\>)** | 删除此集合中,满足给定谓词predicate的所有元素 |
| **boolean removeIf(Predicate\<? super E\>)** | 删除此集合中,满足给定断言predicate的所有元素 |
| **Stream\<E\> stream() Stream\<E\> parallelStream()** | 返回由该 **Collection** 中元素所组成的一个 **Stream** |
| **int size()** | 返回集合中所包含元素的个数 |
| **Object[] toArrat()** | 返回包含该集合所有元素的一个数组 |