“核心技术Ⅱ:流”的版本间差异
 (→相关方法)  | 
				|||
| (未显示同一用户的28个中间版本) | |||
| 第49行: | 第49行: | ||
#: 例如,如果我们只想查找前5 个长单词而不是所有长单词,那么filter 方法就会在匹配到第5 个单词后停止过滤。因此,我们甚至可以操作无限流。  | #: 例如,如果我们只想查找前5 个长单词而不是所有长单词,那么filter 方法就会在匹配到第5 个单词后停止过滤。因此,我们甚至可以操作无限流。  | ||
'''相关方法:'''  | :'''相关方法:'''  | ||
{| class="wikitable" style="width:100%;"  | :{| class="wikitable" style="width:100%;"  | ||
|-  | |-  | ||
! colspan="2"  style="text-align:left;"| java.util.stream.Stream<T> 8  | ! colspan="2"  style="text-align:left;"| java.util.stream.Stream<T> 8  | ||
| 第69行: | 第69行: | ||
== 流的创建 ==  | == 流的创建 ==  | ||
#   | # '''集合转换为流''':  | ||
#:   | #: 用“'''Collection.stream()'''”方法将任何集合转换为一个流。  | ||
#   | # '''数组转换为流''':  | ||
##   | ## 使用“'''Array.stream(array, from, to)'''”可以从数组中位于 from (包括)和 to (不包括)的元索中创建一个流。  | ||
##   | ## 使用 Stream 静态的“'''of'''”方法,将数组转换为流:  | ||
##: <syntaxhighlight lang="java">  | ##: <syntaxhighlight lang="java">  | ||
Stream<String> words= Stream.of(contents.split("\\PL+"));   // split returns a String[] array  | Stream<String> words= Stream.of(contents.split("\\PL+"));   // split returns a String[] array  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
##* of   | ##* of 方法具有【可变长参数】,因此我们可以构建具有任意数值引元的流:  | ||
##*: <syntaxhighlight lang="java">  | ##*: <syntaxhighlight lang="java">  | ||
Stream<String> song = Stream.of("gently", "down", "the", "stream");  | Stream<String> song = Stream.of("gently", "down", "the", "stream");  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
#   | # '''空流''':  | ||
#:   | #: 使用静态的 Stream.empty 方法,创建不包含任何元素的流;  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
Stream<String> silence = Stream.empty();   // Generic type <String> is inferred; same as St ream. <St ri ng>empty()  | Stream<String> silence = Stream.empty();   // Generic type <String> is inferred; same as St ream. <St ri ng>empty()  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
#   | # '''无限流''':  | ||
## generate   | ## '''generate''' 方法:接受一个不包含任何引元的函数(或者从技术上讲,是一个 Supplier<T> 接口的对象)。  | ||
##: <syntaxhighlight lang="java">  | ##: <syntaxhighlight lang="java">  | ||
Stream<String> echos = Stream.generate(() -> "Echo");   // 获得一个常批值的流:  | Stream<String> echos = Stream.generate(() -> "Echo");   // 获得一个常批值的流:  | ||
Stream<Double> randoms = Stream.generate(Math:: random);   // 获得一个随机数的流:  | Stream<Double> randoms = Stream.generate(Math:: random);   // 获得一个随机数的流:  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
## iterate 方法:接受一个“种子”值,以及一个函数(从技术上讲,是一个UnaryOperation<T>), 并且会反复地将该函数应用到之前的结果上。  | ## '''iterate''' 方法:接受一个“种子”值,以及一个函数(从技术上讲,是一个UnaryOperation<T>), 并且会反复地将该函数应用到之前的结果上。  | ||
##: <syntaxhighlight lang="java">  | ##: <syntaxhighlight lang="java">  | ||
Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));  | |||
// 第一个元素是种子Biglnteger.ZERO, 第二个元素是f(seed), 即l (作为大整数),下一个元素是f(f(seed)), 即2, 后续以此类推。  | // 第一个元素是种子Biglnteger.ZERO, 第二个元素是f(seed), 即l (作为大整数),下一个元素是f(f(seed)), 即2, 后续以此类推。  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
| 第100行: | 第100行: | ||
Java API中的其他流方法:  | Java API中的其他流方法:  | ||
# Pattern   | # Pattern 类有一个“'''splitAsStream'''”方法,它会按照某个正则表达式来分割一个“CharSequence”对象:  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
Stream<String> words = Pattern  | Stream<String> words = Pattern.compile("\\PL+").splitAsStream(contents);  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
#   | # 静态的“'''Files.lines()'''”方法会返回一个包含了文件中所有行的 Stream:  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
try (Stream<String> lines = Files.lines (path))  | try (Stream<String> lines = Files.lines(path))  | ||
{  | {  | ||
    ...Process lines...  |     ...Process lines...  | ||
| 第112行: | 第112行: | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
'''相关方法:'''  | :'''相关方法:'''  | ||
{| class="wikitable" style="width:100%;"  | :{| class="wikitable" style="width:100%;"  | ||
|-  | |-  | ||
! colspan="2"  style="text-align:left;"| java.util.stream.Stream 8  | ! colspan="2"  style="text-align:left;"| java.util.stream.Stream 8  | ||
| 第142行: | 第142行: | ||
|-  | |-  | ||
| style="width:50%;" | static Stream<String> lines(Path path) 8  | | style="width:50%;" | static Stream<String> lines(Path path) 8  | ||
| style="width:50%;" rowspan="2" |   | | style="width:50%;" rowspan="2" | 产生一个流,它的元素是指定文件中的行,该文件的字符集为 UTF-8,或者为指定的字符集。  | ||
|-  | |-  | ||
| style="width:50%;" | static Stream<String> lines(Path path, Charset cs) 8  | | style="width:50%;" | static Stream<String> lines(Path path, Charset cs) 8  | ||
| 第157行: | 第157行: | ||
=== filter 、map 和 flatMap 方法 ===  | === filter 、map 和 flatMap 方法 ===  | ||
# '''filter''' 转换会产生一个流,它的元素与某种条件相匹配。  | # '''filter''' 转换会产生一个流,它的元素与某种条件相匹配。  | ||
#: filter   | #: filter 的引元是 '''Predicate<T>''', 即从 T 到 boolean 的函数。  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
List<String> wordlist = . . . ;  | List<String> wordlist = . . . ;  | ||
Stream<String> longwords = wordlist.stream().filter(w -> w. length() > 12);  | Stream<String> longwords = wordlist.stream().filter(w -> w.length() > 12);  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
# '''map''' 方法用于按照某种方式来转换流中的值。  | # '''map''' 方法用于按照某种方式来转换流中的值。  | ||
| 第170行: | 第170行: | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
# '''flatMap''' 方法用于“摊平由流构成的流”。  | # '''flatMap''' 方法用于“摊平由流构成的流”。  | ||
#:   | #: (即,'''将包含流的流,变为流''')  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
//   | // 1、map  | ||
Stream<Stream<String>> result = words.stream() .map(w -> letters(w));  | Stream<Stream<String>> result = words.stream().map(w -> letters(w));  | ||
//   | // 2、flatMap  | ||
Stream<String> flatResult = words.stream().flatMap(w -> letters(w));  | Stream<String> flatResult = words.stream().flatMap(w -> letters(w));  | ||
| 第180行: | 第180行: | ||
public static Stream<String> letters(String s)  | public static Stream<String> letters(String s)  | ||
{  | {  | ||
    List<  |     List<String> result = new ArrayList<>();  | ||
    for (int i = O; i < s.length(); i++)  |     for (int i = O; i < s.length(); i++)  | ||
       result.add(s.substring(i, i + 1));  |        result.add(s.substring(i, i + 1));  | ||
| 第187行: | 第187行: | ||
// 若 w 为“[[your][boat]]”;  | // 若 w 为“[[your][boat]]”;  | ||
// 1、结果为“[...["y","o", "u" ,"r"  | // 1、结果为“[...["y","o", "u" ,"r"],["b" ,"o" ,"a" ,"t"],...]”;  | ||
// 2、结果为“[..."y","o","u" ,"r", "b","o" ,"a" ,"t",...],”;  | // 2、结果为“[..."y","o","u" ,"r", "b","o" ,"a" ,"t",...],”;  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
'''相关方法:'''  | :'''相关方法:'''  | ||
{| class="wikitable" style="width:100%;"  | :{| class="wikitable" style="width:100%;"  | ||
|-  | |-  | ||
! colspan="2"  style="text-align:left;"| java.util.stream.Stream 8  | ! colspan="2"  style="text-align:left;"| java.util.stream.Stream 8  | ||
| 第200行: | 第200行: | ||
|-  | |-  | ||
| style="width:50%;" | <R> Stream<R> map(Function<? super T, ? extends R> mapper)  | | style="width:50%;" | <R> Stream<R> map(Function<? super T, ? extends R> mapper)  | ||
| style="width:50%;" |   | | style="width:50%;" | 产生一个流,它包含将 mapper 应用于当前流中所有元素所产生的结果。  | ||
|-  | |-  | ||
| style="width:50%;" | <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)  | | style="width:50%;" | <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)  | ||
| style="width:50%;" |   | | style="width:50%;" | 产生一个流,它是通过将 mapper 应用于当前流中所有元素所产生的结果连接到一起而获得的。(注意,这里的每个结果都是一个流。)  | ||
|}  | |}  | ||
| 第223行: | 第223行: | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
'''相关方法:'''  | :'''相关方法:'''  | ||
{| class="wikitable" style="width:100%;"  | :{| class="wikitable" style="width:100%;"  | ||
|-  | |-  | ||
! colspan="2"  style="text-align:left;"| java:util.stream.Stream 8  | ! colspan="2"  style="text-align:left;"| java:util.stream.Stream 8  | ||
| 第255行: | 第255行: | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
'''相关方法:'''  | :'''相关方法:'''  | ||
{| class="wikitable" style="width:100%;"  | :{| class="wikitable" style="width:100%;"  | ||
|-  | |-  | ||
! colspan="2"  style="text-align:left;"| java.util.stream.Stream 8  | ! colspan="2"  style="text-align:left;"| java.util.stream.Stream 8  | ||
| 第277行: | 第277行: | ||
=== 简单约简 ===  | === 简单约简 ===  | ||
约简是一种终结操作(tennjnal operation),它们会将流约简为可以在程序中使用的非流值。  | |||
*   | * 这些方法返回的是一个类型“'''Optional<T>'''”的值,它要么在其中包装了答案,要么表示没有任何值(因为流碰巧为空)。  | ||
| 第285行: | 第285行: | ||
# “'''max'''”、“'''min'''”方法,会返回流中元素的最大值和最小值;  | # “'''max'''”、“'''min'''”方法,会返回流中元素的最大值和最小值;  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
Optional<String> largest = words.max(String::  | Optional<String> largest = words.max(String::compareToIgnoreCase);  | ||
System.out.println("largest: " + largest.orElse(""));  | System.out.println("largest: " + largest.orElse(""));  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
| 第302行: | 第302行: | ||
boolean aWordStartsWithQ = words.parallel().anyMatch(s -> s.startsWith("Q"));  | boolean aWordStartsWithQ = words.parallel().anyMatch(s -> s.startsWith("Q"));  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
# “'''allMatch'''  | # “'''allMatch'''”方法,在所有元素匹配断言的情况下返回 true;  | ||
# “'''noneMatch'''  | # “'''noneMatch'''”方法,在没有任何元素匹配断言的情况下返回 true;  | ||
=== Optional 类型 ===  | === <span style="color: green">Optional 类型</span> ===  | ||
Optional<T>   | ----  | ||
* Optional<T>   | '''Optional<T> 对象是一种包装器对象,要么包装了类型 T 的对象,要么没有包装任何对象。'''  | ||
* Optional<T> 类型被当作一种更安全的方式,用来替代类型 T 的引用,这种引用要么引用某个对象,要么为 null。  | |||
====   | ==== 如何使用 Optional 值 ====  | ||
使用 Optional 的关键:  | |||
#   | # 在值不存在的情况下会产生一个可替代物:  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
// 在没有任何匹配时:使用某种默认值,可能是空字符串:  | // 在没有任何匹配时:使用某种默认值,可能是空字符串:  | ||
| 第320行: | 第321行: | ||
// 在没有任何值时抛出异常:  | // 在没有任何值时抛出异常:  | ||
String   | String resu1t = optiona1String.orElseThrow(Illega1StateException::new);   // Supply a method that yie1ds an exception object  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
#   | # 在值存在的情况下才会使用这个值:  | ||
#  | #: “'''ifPresent'''”方法:接受一个函数。如果该可选值存在,那么它会被传递给该函数。否则不会发生任何事情。  | ||
#*  | #*(当调用 ifPresent 时,从该函数不会返回任何值。如果想要处理函数的结果,应该使用 '''map''')  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
//   | // 1、使用 ifPresent 来调用函数处理 optionalValue  | ||
optionalValue.ifPresent(v -> Process v);   // 调用Process处理v  | optionalValue.ifPresent(v -> Process v);   // 调用Process处理v  | ||
| 第334行: | 第335行: | ||
//   | // 当调用 ifPresent 时,从该函数不会返回任何值。如果想要处理函数的结果,应该使用 map:  | ||
Optional<Boo1ean> added = optianalVa1ue.map(results::add);  | Optional<Boo1ean> added = optianalVa1ue.map(results::add);  | ||
// 现在added 具有三种值之一:  | // 现在added 具有三种值之一:  | ||
| 第340行: | 第341行: | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
'''相关方法:'''  | :'''相关方法:'''  | ||
{| class="wikitable" style="width:100%;"  | :{| class="wikitable" style="width:100%;"  | ||
|-  | |-  | ||
! colspan="2"  style="text-align:left;"| java.util.Optional 8  | ! colspan="2"  style="text-align:left;"| java.util.Optional 8  | ||
|-  | |-  | ||
| style="width:50%;" | T orElse(T other)  | | style="width:50%;" | T '''orElse'''(T other)  | ||
| style="width:50%;" | 产生这个 Optional 的值,或者在该 Optional 为空时,产生 other。  | | style="width:50%;" | 产生这个 Optional 的值,或者在该 Optional 为空时,产生 other。  | ||
|-  | |-  | ||
| style="width:50%;" | T orElseGet(Supplier<? extends T> other)  | | style="width:50%;" | T '''orElseGet'''(Supplier<? extends T> other)  | ||
| style="width:50%;" | 产生这个 Optional   | | style="width:50%;" | 产生这个 Optional 的值,或者在该 Optional 为空时,产生调用 other 的结果。  | ||
|-  | |-  | ||
| style="width:50%;" | <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)  | | style="width:50%;" | <X extends Throwable> T '''orElseThrow'''(Supplier<? extends X> exceptionSupplier)  | ||
| style="width:50%;" | 产生这个 Optional 的值,或者在该 Optional 为空时,抛出调用 exceptionSupplier 的结果。  | | style="width:50%;" | 产生这个 Optional 的值,或者在该 Optional 为空时,抛出调用 exceptionSupplier 的结果。  | ||
|-  | |-  | ||
| style="width:50%;" | void ifPresent(Consumer<? super T>   | | style="width:50%;" | void '''ifPresent'''(Consumer<? super T> consumer)  | ||
| style="width:50%;" | 如果该 Optional 不为空,那么就将它的值传递给 consumer 。  | | style="width:50%;" | 如果该 Optional 不为空,那么就将它的值传递给 consumer 。  | ||
|-  | |-  | ||
| style="width:50%;" | <nowiki><U>   | | style="width:50%;" | <nowiki><U> Optional<U></nowiki> '''map'''(Function<? super T , ? extends U> mapper)  | ||
| style="width:50%;" | 产生将该 Optional 的值传递给 mapper 后的结果,只要这个 Optional 不为空且结果不为 null , 否则产生一个空 Optional。  | | style="width:50%;" | 产生将该 Optional 的值传递给 mapper 后的结果,只要这个 Optional 不为空且结果不为 null , 否则产生一个空 Optional。  | ||
|}  | |}  | ||
====   | ==== 不适合使用 Optional 值的方式 ====  | ||
 如果没有正确地使用 Optional 值,那么相比较以往的得到“某物或null”的方式,你并没有得到任何好处。  | |||
get   | # '''get''' 方法会在 Optional 值存在的情况下获得其中包装的元素,或者在不存在的情况下抛出一个 '''NoSuchElementException''' 对象。  | ||
    Optional <T> optionalValue = ...;  | #: 因此,  | ||
#: <syntaxhighlight lang="Java" highlight="">  | |||
    Optional<T> optionalValue = ...;  | |||
    optionalValue.get().someMethod();  |     optionalValue.get().someMethod();  | ||
并不比下面的方式更安全:  | </syntaxhighlight>  | ||
#: 并不比下面的方式更安全:  | |||
#: <syntaxhighlight lang="Java" highlight="">  | |||
    T value = ...;  |     T value = ...;  | ||
    va1ue.someMethod();  |     va1ue.someMethod();  | ||
</syntaxhighlight>  | |||
isPresent   | # '''isPresent''' 方法会报告某个 Optional<T> 对象是否具有一个值。  | ||
    if (optionalVa1ue.isPresent()) optionalValue.get().someMethod();  | #: 但是,  | ||
并不比下面的方式更容易处理:  | #: <syntaxhighlight lang="Java" highlight="">  | ||
    if (value != null) value.someMethod();  |     if(optionalVa1ue.isPresent())    | ||
</  |       optionalValue.get().someMethod();  | ||
</syntaxhighlight>  | |||
#: 并不比下面的方式更容易处理:  | |||
#: <syntaxhighlight lang="Java" highlight="">  | |||
    if (value != null)    | |||
      value.someMethod();  | |||
</syntaxhighlight>  | |||
'''相关方法:'''  | :'''相关方法:'''  | ||
{| class="wikitable" style="width:100%;"  | :{| class="wikitable" style="width:100%;"  | ||
|-  | |-  | ||
! colspan="2"  style="text-align:left;"| java.util.Optional 8  | ! colspan="2"  style="text-align:left;"| java.util.Optional 8  | ||
|-  | |-  | ||
| style="width:50%;" | T get()  | | style="width:50%;" | T '''get'''()  | ||
| style="width:50%;" | 产生这个 Optional 的值,或者在该 Optional 为空时, 抛出一个 NoSuchElementException 对象。  | | style="width:50%;" | 产生这个 Optional 的值,或者在该 Optional 为空时, 抛出一个 NoSuchElementException 对象。  | ||
|-  | |-  | ||
| style="width:50%;" | boolean isPresent()  | | style="width:50%;" | boolean '''isPresent'''()  | ||
| style="width:50%;" | 如果该 Optional 不为空,则返回 true。  | | style="width:50%;" | 如果该 Optional 不为空,则返回 true。  | ||
|}  | |}  | ||
====   | ==== 创建 Optional 值 ====  | ||
有多个方法可以创建 Optional 对象:  | |||
# “Optional.of(result)”  | # “Optional.'''of'''(result)”  | ||
# “Optional.empty()”  | # “Optional.'''empty'''()”  | ||
# “Optional.ofNullable(obj)  | # “Optional.'''ofNullable'''(obj)”:在 obj 不为 null 的情况下返回“Optional.of(obj)”, 否则会返回“Optional.empty()”。  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
public static Optional<Double> inverse(Double x)  | public static Optional<Double> inverse(Double x)  | ||
| 第403行: | 第412行: | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
'''相关方法:'''  | :'''相关方法:'''  | ||
{| class="wikitable" style="width:100%;"  | :{| class="wikitable" style="width:100%;"  | ||
|-  | |-  | ||
! colspan="2"  style="text-align:left;"| java.util.Optional 8  | ! colspan="2"  style="text-align:left;"| java.util.Optional 8  | ||
|-  | |-  | ||
| style="width:50%;" | static <T> Optional<T> of(T value)  | | style="width:50%;" | static <T> Optional<T> '''of'''(T value)  | ||
| style="width:50%;" rowspan="2" | 产生一个具有给定值的 Optional。  | | style="width:50%;" rowspan="2" | 产生一个具有给定值的 Optional。  | ||
* 如果 value 为 nul1, 那么第一个方法会抛出一个 NullPointerException 对象, 而第二个方法会产生一个空 Optional。  | * 如果 value 为 nul1, 那么第一个方法会抛出一个 NullPointerException 对象, 而第二个方法会产生一个空 Optional。  | ||
|-  | |-  | ||
| style="width:50%;" | static <T> Optional<T> ofNullable(T value)  | | style="width:50%;" | static <T> Optional<T> '''ofNullable'''(T value)  | ||
|-  | |-  | ||
| style="width:50%;" | static <T> Optional<T> empty()  | | style="width:50%;" | static <T> Optional<T> '''empty'''()  | ||
| style="width:50%;" | 产生一个空 Optional。  | | style="width:50%;" | 产生一个空 Optional。  | ||
|}  | |}  | ||
====   | ==== 用 flatMap 来构建 Optional 值的函数 ====  | ||
*(类比于“stream.flatMap()”)  | *(类比于“stream.flatMap()”)  | ||
“Optional.flatMap()”用来'''将流计算过程中的方法连接起来''':  | |||
<pre>  | <pre>  | ||
假设你有一个可以产生 Optional<T> 对象的方法 f, 并且目标类型 T 具有一个可以产生 Optional<U> 对象的方法 g。如果它们都是普通的方法,那么你可以通过调用“s.f().g()”来将它们组合起来。  | |||
但是这种组合没法工作,因为“s.f()  | 但是这种组合没法工作,因为“s.f()”的类型为 Optional<T>, 而不是 T。因此,需要调用:  | ||
    Optional<U> result= s.f().flatMap(T::g);  |     Optional<U> result= s.f().flatMap(T::g);  | ||
    如果 s.f() 的值存在,那么 g 就可以应用到它上面。否则,就会返回一个空 Optional<U> 。  | |||
很明显,如果有更多的可以产生 Optional 值的方法或 Lambda 表达式,那么就可以重复此过程。  | |||
</pre>  | </pre>  | ||
可以直接将对 flatMap 的调用链接起来,从而构建由这些步骤构成的管道,只有所有步骤都成功时,该管道才会成功。  | |||
<syntaxhighlight lang="java">  | <syntaxhighlight lang="java">  | ||
| 第445行: | 第454行: | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
'''相关方法:'''  | :'''相关方法:'''  | ||
{| class="wikitable" style="width:100%;"  | :{| class="wikitable" style="width:100%;"  | ||
|-  | |-  | ||
! colspan="2"  style="text-align:left;"| java.util.Optional 8  | ! colspan="2"  style="text-align:left;"| java.util.Optional 8  | ||
|-  | |-  | ||
| style="width:50%;" | <nowiki><U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper)  | | style="width:50%;" | <nowiki><U> Optional<U></nowiki> '''flatMap'''(Function<? super T,Optional<U>> mapper)  | ||
| style="width:50%;" | 产生将 mapper 应用于当前的 Optional 值所产生的结果,或者在当前 Optional 为空时,返回一个空 Optional。  | | style="width:50%;" | 产生将 mapper 应用于当前的 Optional 值所产生的结果,或者在当前 Optional 为空时,返回一个空 Optional。  | ||
|}  | |}  | ||
=== 收集结果 ===  | === 收集结果 ===  | ||
----  | |||
当处理完流之后,通常会想要查看、收集其元素。  | 当处理完流之后,通常会想要查看、收集其元素。  | ||
| 第466行: | 第476行: | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
'''相关方法:'''  | :'''相关方法:'''  | ||
{| class="wikitable" style="width:100%;"  | :{| class="wikitable" style="width:100%;"  | ||
|-  | |-  | ||
! colspan="2"  style="text-align:left;"| java.util.stream.BaseStream 8  | ! colspan="2"  style="text-align:left;"| java.util.stream.BaseStream 8  | ||
| 第482行: | 第492行: | ||
|}  | |}  | ||
====   | ==== 收集元素到集合(Collection) ====  | ||
# “'''stream.toArray()'''”:收集到数组中:  | # “'''stream.toArray()'''”:收集到数组中:  | ||
#*(如果想要让数组具有正确的类型,可以将其传递到数组构造器中)  | #*(如果想要让数组具有正确的类型,可以将其传递到数组构造器中)  | ||
| 第493行: | 第503行: | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
# “'''stream.collect()'''”:收集到其他目标中;  | # “'''stream.collect()'''”:收集到其他目标中;  | ||
#*   | #* 该方法接受—个“Collector”接口的实例(Collectors类提供了大量用于生成公共收集器的工厂方法);  | ||
## 收集到列表或集:  | ## 收集到列表或集:  | ||
##: <syntaxhighlight lang="java">  | ##: <syntaxhighlight lang="java">  | ||
List<String> resu1t = stream.co11ect(Co11ectors  | List<String> resu1t = stream.co11ect(Co11ectors.toList());  | ||
或  | 或  | ||
Set<String> result = stream.collect(Collectors.toSet());  | Set<String> result = stream.collect(Collectors.toSet());  | ||
| 第516行: | 第526行: | ||
String result = stream.map(Object::toString).collect(Collectors.joining(", "));  | String result = stream.map(Object::toString).collect(Collectors.joining(", "));  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
## 如果想要将流的结果约简为总和、平均值、最大值或最小值,可以使用“summarizing(  | ## 如果想要将流的结果约简为总和、平均值、最大值或最小值,可以使用“summarizing(Int | Long | Double)”方法中的某一个。  | ||
##*(这些方法会接受一个将流对象映射为数据的函数,同时,这些方法会产生类型为“(Int|Long|Double)SummaryStatistics”的结果,同时计算总和、数最、平均值、最小值和最大值。)  | ##*(这些方法会接受一个将流对象映射为数据的函数,同时,这些方法会产生类型为“(Int | Long | Double)SummaryStatistics”的结果,同时计算总和、数最、平均值、最小值和最大值。)  | ||
##: <syntaxhighlight lang="java">  | ##: <syntaxhighlight lang="java">  | ||
IntSummaryStatistics summary = stream.co11ect(Co11ectors.summarizingInt(String::length));  | IntSummaryStatistics summary = stream.co11ect(Co11ectors.summarizingInt(String::length));  | ||
| 第524行: | 第534行: | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
'''相关方法:'''  | :'''相关方法:'''  | ||
{| class="wikitable" style="width:100%;"  | :{| class="wikitable mw-collapsible mw-collapsed" style="width:100%;"  | ||
|-  | |-  | ||
! colspan="2"  style="text-align:left;"| java.util.stream.BaseStream 8  | ! colspan="2"  style="text-align:left;"| java.util.stream.BaseStream 8  | ||
| 第552行: | 第562行: | ||
| style="width:50%;" rowspan="3" | 产生一个连接字符串的收集器。  | | style="width:50%;" rowspan="3" | 产生一个连接字符串的收集器。  | ||
* 分隔符会翌于字符串之间,而第一个字符串之前可以有前缀,最后一个字符串之后可以有后缀。  | * 分隔符会翌于字符串之间,而第一个字符串之前可以有前缀,最后一个字符串之后可以有后缀。  | ||
*  | * 如果没有指定分隔符,那么它们都为空。  | ||
|-  | |-  | ||
| style="width:50%;" | static Collector<CharSequence, ?, String> joining(CharSequence delimiter)  | | style="width:50%;" | static Collector<CharSequence, ?, String> joining(CharSequence delimiter)  | ||
| 第559行: | 第569行: | ||
|-  | |-  | ||
| style="width:50%;" | static <T> Collector<T, ?, IntSumnaryStatisties> sumnarizingInt(ToIntFunction<? super T> mapper)  | | style="width:50%;" | static <T> Collector<T, ?, IntSumnaryStatisties> sumnarizingInt(ToIntFunction<? super T> mapper)  | ||
| style="width:50%;" rowspan="3" | 产生能够生成 (Int | Long | Double)SummaryStatistics 对象的收集器,通过它可以获得将 mapper   | | style="width:50%;" rowspan="3" | 产生能够生成 (Int | Long | Double)SummaryStatistics 对象的收集器,通过它可以获得将 mapper 应用于每个元素后所产生的结果的个数、总和、平均值、最大值和最小值。  | ||
|-  | |-  | ||
| style="width:50%;" | static <T> Collector<T, ?, LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper)  | | style="width:50%;" | static <T> Collector<T, ?, LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper)  | ||
| 第580行: | 第590行: | ||
|-  | |-  | ||
| style="width:50%;" | (int | long | double) getMax ()  | | style="width:50%;" | (int | long | double) getMax ()  | ||
| style="width:50%;" rowspan="2" | 产生汇总后的元素的最大值和最小值,或者在没有任何元素时,产生 (Integer | Long | Double).(  | | style="width:50%;" rowspan="2" | 产生汇总后的元素的最大值和最小值,或者在没有任何元素时,产生 (Integer | Long | Double).(MAX | MIN)_VALUE。  | ||
|-  | |-  | ||
| style="width:50%;" | (int | long | double) getMin()  | | style="width:50%;" | (int | long | double) getMin()  | ||
|}  | |}  | ||
====   | ==== 收集到映射表(Map) ====  | ||
“'''Collectors.toMap'''”方法有两个函数引元,它们用来产生映射表的键和值。  | “'''Collectors.toMap'''”方法有两个函数引元,它们用来产生映射表的键和值。  | ||
<syntaxhighlight lang="java">  | <syntaxhighlight lang="java">  | ||
Map<Integer, String> idToName = people.collect(Co11ectors.toMap(Person::getId, Person::getName));  | Map<Integer, String> idToName = people.collect(Co11ectors.toMap(Person::getId, Person::getName));  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
#   | # 通常情况下,值应该是实际的元素,因此第二个函数可以使用“Function.identity()”;  | ||
#:(“Function.identity()  | #:(“Function.identity()”:返回一个输出跟输入一样的 Lambda 表达式对象,等价于形如“t -> t”形式的 Lambda 表达式)  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
Map<Integer, String> idToPerson = peop1e.collect(Collectors.toMap(Person::getId, Function.identity()));  | Map<Integer, String> idToPerson = peop1e.collect(Collectors.toMap(Person::getId, Function.identity()));  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
#   | # 如果有多个元素具有相同的键,那么就会存在冲突,收集器将会抛出一个“'''IllegalStateException'''”对象。  | ||
#:   | #: 可以通过提供第 3 个函数引元来覆盖这种行为:  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
Stream<Locale> loca1es = Stream.of(Locale.getAvailablelocales());  | Stream<Locale> loca1es = Stream.of(Locale.getAvailablelocales());  | ||
| 第605行: | 第615行: | ||
       (existingValue, newValue) -> existingValue));  |        (existingValue, newValue) -> existingValue));  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
#   | # 如果想要得到 TreeMap, 那么可以将构造器作为第 4 个引元来提供(合并函数):  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
Map<lnteger, Person> idToPerson = people.collect(  | Map<lnteger, Person> idToPerson = people.collect(  | ||
| 第615行: | 第625行: | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
'''相关方法:'''  |  对于每一个 toMap 方法,都有一个等价的可以产生'''并发映射表'''的“'''toConcurrentMap'''”方法。【???】  | ||
{| class="wikitable" style="width:100%;"  | * 单个并发映射表可以用于并行集合处理。  | ||
* 当使用并行流时,共享的映射表比合并映射表要更高效。  | |||
* 元素不再是按照流中的顺序收集的(但是通常这不会有什么问题)。  | |||
:'''相关方法:'''  | |||
:{| class="wikitable mw-collapsible mw-collapsed" style="width:100%;"  | |||
|-  | |-  | ||
! colspan="2"  style="text-align:left;"| java.util.stream.Collector 8  | ! colspan="2"  style="text-align:left;"| java.util.stream.Collector 8  | ||
|-  | |-  | ||
| style="width:50%;" |   | | style="width:50%;" | static <T, K, U> Collector<T, ?, Map<K, U>> '''toMap'''(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)  | ||
| style="width:50%;" rowspan="6" | 产生一个收集器,它会产生一个映射表或并发映射表。  | | style="width:50%;" rowspan="6" | 产生一个收集器,它会产生一个映射表或并发映射表。  | ||
* keyMapper 和valueMapper 函数会应用于每个收集到的元素上,从而在所产生的映射表中生成一个键/值项。  | * keyMapper 和valueMapper 函数会应用于每个收集到的元素上,从而在所产生的映射表中生成一个键/值项。  | ||
| 第631行: | 第642行: | ||
* 默认情况下,其结果是一个 HashMap 或 ConcurrentHashMap 。你可以提供一个 mapSupplier, 它会产生所期望的映射表实例。  | * 默认情况下,其结果是一个 HashMap 或 ConcurrentHashMap 。你可以提供一个 mapSupplier, 它会产生所期望的映射表实例。  | ||
|-  | |-  | ||
| style="width:50%;" |   | | style="width:50%;" | static <T, K, U> Collector<T, ?, Map<K, U>> '''toMap'''(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<nowiki><U></nowiki> mergeFunction)  | ||
|-  | |-  | ||
| style="width:50%;" |   | | style="width:50%;" | static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> '''toMap'''(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<nowiki><U></nowiki> mergeFunction, Supplier<M> mapSupplier)  | ||
|-  | |-  | ||
| style="width:50%;" |   | | style="width:50%;" | static <T, K, U> Collector<T, ?, ConcurrentMap<K, U>> '''toConcurrentMap'''(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)  | ||
|-  | |-  | ||
| style="width:50%;" |   | | style="width:50%;" | static <T, K, U> Collector<T, ?, ConcurrentMap<K, U>> '''toConcurrentMap'''(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<nowiki><U></nowiki> mergeFunction)  | ||
|-  | |-  | ||
| style="width:50%;" |   | | style="width:50%;" | static <T, K, U, M extends ConcurrentMap<K, U>> Collector<T, ?, M> '''toConcurrentMap'''(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<nowiki><U></nowiki> mergeFunction, Supplier<M> mapSupplier)  | ||
|}  | |}  | ||
=== 群组和分区 ===  | === 群组和分区 ===  | ||
# “'''groupingBy'''  | ----  | ||
# “'''groupingBy'''”方法,将具有相同特性的值群聚成组;('''群组''')  | |||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
Map<String, List<Locale>> countryTolocales = 1oca1es.collect(Co11ectors.groupingBy(loca1e::getCountry));  | Map<String, List<Locale>> countryTolocales = 1oca1es.collect(Co11ectors.groupingBy(loca1e::getCountry));  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
# “'''partitioningBy'''  | # “'''partitioningBy'''”方法;('''分区''')  | ||
#:   | #: 当分类函数是断言函数(即返回 boolean 值的函数)时,流的元素可以分区为两个列表:该函数返回 true 的元素和其他的元素。在这种情况下,使用 partitioningBy 比使用 groupingBy 要更高效。  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
Map<Boolean, List<locale>> englishAndOtherlocales = locales.collect(Collectors.partitioningBy(l -> l.getlanguage().equals("en")));  | Map<Boolean, List<locale>> englishAndOtherlocales = locales.collect(Collectors.partitioningBy(l -> l.getlanguage().equals("en")));  | ||
List<locale> englishloca1es = englishAndOtherlocales.get(true);  | List<locale> englishloca1es = englishAndOtherlocales.get(true);  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
*   | * 如果调用 '''groupingByConcurrent''' 方法,就会'''在使用并行流时获得一个被并行组装的并行映射表'''。这与 toConcurrentMap 方法完全类似。  | ||
:'''相关方法:'''  | |||
:{| class="wikitable" style="width:100%;"  | |||
|-  | |||
! colspan="2"  style="text-align:left;"| java.util.stream.Collector 8  | |||
|-  | |||
| style="width:50%;" | static <T, K> Collector<T, ?, Map<K,List<T>>> '''groupingBy'''(Function<? super T, ? extends K> classifier)  | |||
| style="width:50%;" rowspan="2" | 产生一个收集器,它会产生一个映射表或并发映射表,其键是将 classifier 应用于所有收集到的元素上所产生的结果,而值是由具有相同键的元素构成的一个个列表。  | |||
|-  | |||
| style="width:50%;" | static <T, K> Collector<T, ?, ConcurrentMap<K, List<T>>> '''groupingByConcurrent'''(Function<? super T, ? extends K> classifier)  | |||
|-  | |||
| style="width:50%;" | static <T> Collector<T, ?, Map<Boolean, List<T>>> '''partitioningBy'''(Predicate<? super T> predicate)  | |||
| style="width:50%;" | 产生一个收集器,它会产生一个映射表,其键是 true/false , 而值是由 满足/不满足 断言的元素构成的列表。  | |||
|}  | |||
==== 下游收集器【???】 ====  | |||
groupingBy 方法会产生一个映射表(Map),它的每个值都是一个列表(List)。如果想要以某种方式来处理这些列表,就需要提供一个“下游收集器”。  | |||
: 例如,如果想耍获得集(Set)而不是列表(List),那么可以使用上一节中看到的“Collector.toSet”收集器:  | |||
: <syntaxhighlight lang="java">  | |||
Map<String, Set<Locale>> countrylolocaleSet = locales.collect(groupingBy(Locale::getCountry, toSet()));  | |||
<syntaxhighlight lang="java">  | |||
Map<String, Set<Locale>> countrylolocaleSet = locales.collect(  | |||
</syntaxhighlight>  | </syntaxhighlight>  | ||
Java   | Java 提供了多种可以将群组元素约简为数字的收集器:(方法均为“'''java.util.stream.Collectors.xxx'''”)  | ||
# “counting”会产生收集到的元素的个数:  | # “counting”会产生收集到的元素的个数:  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
Map<String, Long> countryToLocaleCounts = locales.co11ect(  | Map<String, Long> countryToLocaleCounts = locales.co11ect(  | ||
   groupingBy(Locale::getCountry, counting()));  | |||
//   | // 可以对每个国家有多少个 Locale 进行计数。  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
# “summing(Int|Long|Double)”会接受一个函数作为引元,将该函数应用到下游元素中,并产生它们的和:  | # “summing(Int | Long | Double)”会接受一个函数作为引元,将该函数应用到下游元素中,并产生它们的和:  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
Map<String, Integer> stateToCityPopulation = cities.collect(  | Map<String, Integer> stateToCityPopulation = cities.collect(  | ||
| 第688行: | 第706行: | ||
# “maxBy”和“minBy”会接受一个比较器,并产生下游元素中的最大值和最小值。例如:  | # “maxBy”和“minBy”会接受一个比较器,并产生下游元素中的最大值和最小值。例如:  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
Map<String, Optional<City>>   | Map<String, Optional<City>> stateToLargestCity = cities.co11ect(  | ||
    groupingBy(City::getState, maxBy(Comparator.comparing(City::getPopulation))));  |     groupingBy(City::getState, maxBy(Comparator.comparing(City::getPopulation))));  | ||
//   | // 可以产生每个州中最大的城市。  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
# “mapping”方法会产生将函数应用到下游结果上的收集器, 并将函数值传递给另一个收集器:  | # “mapping”方法会产生将函数应用到下游结果上的收集器, 并将函数值传递给另一个收集器:  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
Map<String, Optional<String>>   | Map<String, Optional<String>> stateToLongestCityName = cities.collect(  | ||
    groupingBy(City::getState,  |     groupingBy(City::getState,  | ||
       mapping(City::getName,  |        mapping(City::getName,  | ||
| 第701行: | 第719行: | ||
* 将收集器组合起来是一种很强大的方式,但是它也可能会导致产生非常复杂的表达式。  | * 将收集器组合起来是一种很强大的方式,但是它也可能会导致产生非常复杂的表达式。  | ||
*:   | *: 它们的最佳用法是与 groupingBy 和 partitioningBy 一起处理“下游的”映射表中的值。  | ||
*:   | *: 否则,应该直接在流上应用诸如 map、reduce、count、max 或 min 这样的方法。  | ||
====  | :'''相关方法:'''  | ||
java.util.stream.Collectors 8  | :{| class="wikitable" style="width:100%;"  | ||
|-  | |||
! colspan="2"  style="text-align:left;"| java.util.stream.Collectors 8  | |||
|-  | |||
| style="width:50%;" | static <T> Collector<T, ?, Long> '''counting'''()  | |||
| style="width:50%;" | 产生一个可以对收集到的元素进行计数的收集器。  | |||
|-  | |||
| style="width:50%;" | static <T> Collector<T, ?, Integer> '''summingInt'''(ToIntFunction<? super T> mapper)  | |||
| style="width:50%;" rowspan="3" | 产生一个收集器,对将 mapper 应用到收集到的元素上之后产生的值计算总和。  | |||
|-  | |||
| style="width:50%;" | static <T> Collector<T, ?, Long> '''summingLong'''(ToLongFunction<? super T> mapper)  | |||
|-  | |||
| style="width:50%;" | static <T> Col1ector<T, ?, Double> '''summingDouble'''(ToDoubleFunction<? super T> mapper)  | |||
|-  | |||
| style="width:50%;" | static <T> Collector<T, ?, Optiona1<T>> '''maxBy'''(Comparator<? super T> comparator)  | |||
| style="width:50%;" rowspan="2" | 产生一个收集器,使用 comparator 指定的排序方法,计算收集到的元素中的最大值和最小值。  | |||
|-  | |||
| style="width:50%;" | static <T> Collector<T, ?, Optional<T>> '''minBy'''(Comparator<? super T> comparator)  | |||
|-  | |||
| style="width:50%;" | static <T, U, A, R> Collector<T , ? , R> '''mapping'''(Function<? super T, ? extends U> mapper, Collector<? super U, A, R> downstream)  | |||
| style="width:50%;" | 产生一个收集器,它会产生一个映射表,其键是将 mapper 应用到收集到的数据上而产生的,其值是使用 downstream 收集器收集到的具有相同键的元素。  | |||
|}  | |||
== 其他 ==  | == 其他 ==  | ||
=== 约简操作 ===  | === 约简操作 ===  | ||
# '''“reduce”方法''':是一种用于从流中计算某个值的通用机制,其最简单的形式将接受一个二元函数,并从前两个元素开始待续应用它。<br/>  | # '''“reduce”方法''':是一种用于从流中计算某个值的通用机制,其最简单的形式将接受一个二元函数,并从前两个元素开始待续应用它。<br/>  | ||
#*   | #* 如果 reduce 方法有一项约简操作 op,那么该约简就会产生“v0 op v1 op v2 op ...”,其中我们将函数调用“op(vi,vi+1)”写作“vi op vi+1”。  | ||
#* 操作应该是可结合的:即组合元素时使用的顺序不应该成为问题【在数学标记法中,(x op y) op z 必须等于 x op (y op z)。即:操作顺序可变,但元素顺序不可变】。这使得在使用并行流时,可以执行高效的约简。  | #* 操作应该是可结合的:即组合元素时使用的顺序不应该成为问题【在数学标记法中,(x op y) op z 必须等于 x op (y op z)。即:操作顺序可变,但元素顺序不可变】。这使得在使用并行流时,可以执行高效的约简。  | ||
#* 减法是一个不可结合操作的例子,例如,(6-3)-2 != 6-(3-2) 。  | #* 减法是一个不可结合操作的例子,例如,(6-3)-2 != 6-(3-2) 。  | ||
| 第729行: | 第758行: | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
# 通常,会有一个'''幺元值“e”''':使得“e op x = x”,可以使用这个元素作为计算的起点:  | # 通常,会有一个'''幺元值“e”''':使得“e op x = x”,可以使用这个元素作为计算的起点:  | ||
#*   | #* 如果流为空,则会返回幺元值,就再也不需要处理 Optional 类了。  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
List<lnteger> values= ... ;  | List<lnteger> values= ... ;  | ||
| 第741行: | 第770行: | ||
    (total1, total2) -> totall + total2);  |     (total1, total2) -> totall + total2);  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
#   | # 实践中,可能并不会频繁地用到 reduce 方法。通常,'''映射为数字流'''并使用其方法来计算总和、最大值和最小值会更容易:  | ||
#* 因为它不涉及装箱操作,所以更简单也更高效;  | #* 因为它不涉及装箱操作,所以更简单也更高效;  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
words.mapToInt(String::length).sum(),  | words.mapToInt(String::length).sum(),  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
#   | # 有时 reduce 会显得并不够通用,而应使用'''“collect”方法''':  | ||
#* 它会接受单个引元:  | #* 它会接受单个引元:  | ||
#*#   | #*# 一个'''提供者''',它会创建目标类型的新实例,例如散列集的构造器。  | ||
#*#   | #*# 一个'''累积器''',它会将一个元素添加到一个实例上,例如 add 方法。  | ||
#*#   | #*# 一个'''组合器''',它会将两个实例合并成一个,例如 addAll。  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
BitSet result = stream.co11ect(BitSet::new, BitSet::set, BitSet::or);  | BitSet result = stream.co11ect(BitSet::new, BitSet::set, BitSet::or);  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
=====   | :'''相关方法:'''  | ||
:{| class="wikitable" style="width:100%;"  | |||
* T reduce(T identity, BinaryOperator<T> accumulator)  | |-  | ||
! colspan="2"  style="text-align:left;"| java.util.Stream 8  | |||
|-  | |||
| style="width:50%;" | Optional<T> '''reduce'''(BinaryOperator<T> accumulator)  | |||
*  | | style="width:50%;" rowspan="3" | 用给定的 accumulator 函数产生流中元素的累积总和。  | ||
* 如果提供了幺元,那么第一个被累计的元素就是该么元。  | |||
* 如果提供了组合器,那么它可以用来将分别累积的各个部分整合成总和。  | |||
|-  | |||
| style="width:50%;" | T '''reduce'''(T identity, BinaryOperator<T> accumulator)  | |||
|-  | |||
| style="width:50%;" | <nowiki><U></nowiki> U '''reduce'''<nowiki>(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)</nowiki>  | |||
|-  | |||
| style="width:50%;" | <nowiki><R></nowiki> R '''collect'''<nowiki>(Supplier<R> supplier, BiConsumer<R, ? superT> accumulator, BiConsumer<R ,R> combiner)</nowiki>  | |||
| style="width:50%;" | 将元素收集到类型 R 的结果中。  | |||
* 在每个部分上,都会调用 supplier 来提供初始结果,调用 accumulator 来交替地将元素添加到结果中,并调用 combiner 来整合两个结果。  | |||
|}  | |||
=== 基本类型流 ===  | === 基本类型流 ===  | ||
基本类型流:  | 基本类型流:  | ||
#   | # IntStream、LongStream 和 DoubleStream,用来直接存储基本类型值,而无需使用包装器;  | ||
#   | # 存储 short、char、byte 和 boolean,可以使用 IntStream;  | ||
#   | # 对于 float,可以使用 DoubleStream。  | ||
基本类型流的使用:  | 基本类型流的使用:  | ||
#   | # 创建 IntStream,可以使用“IntStream.of”和“Arrays.stream”;  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
IntStream stream = IntStream.of(l, 1, 2, 3, 5);  | IntStream stream = IntStream.of(l, 1, 2, 3, 5);  | ||
stream = Arrays.stream(values, from, to);   // values is an int[] array  | stream = Arrays.stream(values, from, to);   // values is an int[] array  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
#   | # 与对象流一样,我们还可以使用静态的“generate”和“iterate”方法;(无限流)  | ||
#   | # IntStream 和 LongStream 有静态方法“range”和“rangeClosed”,可以生成步长为1 的整数范闱:  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
IntStream zeroToNinetyNine = IntStream.range(O, 100);   // Upper bound is excluded  | IntStream zeroToNinetyNine = IntStream.range(O, 100);   // Upper bound is excluded  | ||
IntStream zeroToHundred = IntStream.rangeClosed(0, 100);   // Upper bound is included  | IntStream zeroToHundred = IntStream.rangeClosed(0, 100);   // Upper bound is included  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
# CharSequence   | # CharSequence 接口拥有“codePoints”和“chars”方法,可以生成由字符的 Unicode 码或由 UTF-16 编码机制的码元构成的 IntStream:  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
String sentence = "\uD83S\u0D46 is the set of octonions.";   // \uD835\uDD46 is the UTF-16 encoding of the letter@, unicode U+1D546  | String sentence = "\uD83S\u0D46 is the set of octonions.";   // \uD835\uDD46 is the UTF-16 encoding of the letter @, unicode U+1D546  | ||
IntStream codes = sentence.codePoints();   // The stream with hex values 1D546 20 69 73 20 . . .  | IntStream codes = sentence.codePoints();   // The stream with hex values 1D546 20 69 73 20 . . .  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
| 第790行: | 第832行: | ||
基本流类型的转换:  | 基本流类型的转换:  | ||
#   | # 将'''“对象流”转换为“基本类型流”''':可以用“'''mapToInt'''”、“'''mapToLong'''”和“'''mapToDouble'''”方法;  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
Stream<String> words = . . . ;  | Stream<String> words = . . . ;  | ||
IntStream lengths = words.mapToInt(String::length);  | |||
</syntaxhighlight>  | </syntaxhighlight>  | ||
#   | # 将'''“基本类型流”转换为“对象流”''':需要用“'''boxed'''”方法;  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
Stream<  | Stream<Integer> integers = IntStream.range(O, 100).boxed();  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
| 第803行: | 第845行: | ||
“基本类型流”与“对象流”的方法类似,但有差异:  | “基本类型流”与“对象流”的方法类似,但有差异:  | ||
# toArray 方法会返回基本类型数组。  | # toArray 方法会返回基本类型数组。  | ||
#   | # 产生可选结果的方法会返回一个 '''OptionalInt''' 、'''OptionalLong''' 或 '''OptionalDouble'''。  | ||
#:   | #: 这些类与 Optional 类类似,但是具有 getAsInt 、getAsLong 和 getAsDouble 方法,而不是 get 方法。  | ||
#   | # 具有返回总和、平均值、最大值和最小值的 sum 、average 、max 和 min 方法。对象流没有定义这些方法。  | ||
# '''summaryStatisties'''   | # '''summaryStatisties''' 方法会产生一个类型为 IntSummaryStatistics 、LongSummaryStatistics 或 DoubleSummaryStatistics 的对象,它们可以同时报告流的总和、平均值、最大值和最小值。  | ||
====  | :'''相关方法:'''  | ||
Java.utit.stream.  | :{| class="wikitable mw-collapsible mw-collapsed" style="width:100%;"  | ||
|-  | |||
! colspan="2"  style="text-align:left;"| Java.utit.stream.IntStream 8  | |||
|-  | |||
| style="width:50%;" | static IntStream '''range'''(int startInclusive, int endExclusive)  | |||
| style="width:50%;" rowspan="2" | 产生一个由给定范围内的整数构成的 IntStream。  | |||
|-  | |||
| style="width:50%;" | static IntStream '''rangeClosed'''(int startInclusive, int endInclusive)  | |||
|-  | |||
| style="width:50%;" | static IntStream '''of'''(int ... values)  | |||
| style="width:50%;" | 产生一个由给定元素构成的 IntStream 。  | |||
|-  | |||
| style="width:50%;" | int[] '''toArray'''()  | |||
| style="width:50%;" | 产生一个由当前流中的元素构成的数组。  | |||
|-  | |||
| style="width:50%;" | int sum()  | |||
Java.util.stream.LongStream 8    | | style="width:50%;" rowspan="5" | 产生当前流中元素的总和、平均值、最大值和最小值,或者从中可以获得这些结果的所有四种值的对象。  | ||
|-  | |||
| style="width:50%;" | OptionalDouble average()  | |||
|-  | |||
| style="width:50%;" | OptionalInt max()  | |||
|-  | |||
| style="width:50%;" | OptionalInt min()  | |||
|-  | |||
| style="width:50%;" | IntSummaryStatistics '''summaryStatistics'''()  | |||
|-  | |||
| style="width:50%;" | Stream<Integer> '''boxed'''()  | |||
| style="width:50%;" | 产生用于当前流中的元素的包装器对象流。  | |||
|-  | |||
! colspan="2"  style="text-align:left;"| Java.util.stream.LongStream 8    | |||
|-  | |||
| style="width:50%;" | static LongStream range(long startInclusive, long endExclusive)  | |||
java.util.stream.DoubleStream 8  | | style="width:50%;" rowspan="2" | 用给定范围内的整数产生一个 LongStream。  | ||
|-  | |||
| style="width:50%;" | static LongStream rangeClosed(long startInclusive , long endlnclusive)  | |||
|-  | |||
| style="width:50%;" | static LongStream of(long .. . values)  | |||
| style="width:50%;" | 用给定元素产生一个 LongStream 。  | |||
|-  | |||
| style="width:50%;" | 1ong[] toArray()  | |||
| style="width:50%;" | 用当前流中的元素产生一个数组。  | |||
|-  | |||
| style="width:50%;" | 1ong sum()  | |||
| style="width:50%;" rowspan="5" | 产生当前流中元素的总和、平均值、最大值和最小值,或者从中可以获得这些结果的所有四种值的对象。  | |||
|-  | |||
java.lang.CharSequence 1.0  | | style="width:50%;" | OptionalDouble average()  | ||
|-  | |||
| style="width:50%;" | OptionalLong max()  | |||
java.util.Random 1.0  | |-  | ||
| style="width:50%;" | OptionalLong min()  | |||
* IntStream ints(int randomNumberOrigin,   | |-  | ||
| style="width:50%;" | LongSummaryStatisties summaryStatisties()  | |||
|-  | |||
| style="width:50%;" | Stream<Long> boxed()  | |||
| style="width:50%;" | 产生用千当前流中的元索的包装器对象流。  | |||
|-  | |||
! colspan="2"  style="text-align:left;"| java.util.stream.DoubleStream 8  | |||
|-  | |||
| style="width:50%;" | static DoubleStream of(double ... values)  | |||
| style="width:50%;" | 用给定元素产生一个 DoubleStream。  | |||
|-  | |||
| style="width:50%;" | double[] toArray()  | |||
java.util.Optional(  | | style="width:50%;" | 用当前流中的元素产生一个数组。  | ||
|-  | |||
| style="width:50%;" | double sum()  | |||
| style="width:50%;" rowspan="5" | 产生当前流中元素的总和、平均值、最大值和最小值,或者从中可以获得这些结果的所有四种值的对象。  | |||
|-  | |||
| style="width:50%;" | OptionalDouble average()  | |||
|-  | |||
| style="width:50%;" | OptionalDouble max()  | |||
|-  | |||
| style="width:50%;" | OptionalDouble min()  | |||
java.util.(Int|Long|Double)SummaryStatistics 8  | |-  | ||
| style="width:50%;" | DoubleSummaryStatistics summaryStatistics()  | |||
|-  | |||
| style="width:50%;" | Stream<Double> boxed()  | |||
| style="width:50%;" | 产生用于当前流中的元素的包装器对象流。  | |||
|-  | |||
! colspan="2"  style="text-align:left;"| java.lang.CharSequence 1.0  | |||
|-  | |||
| style="width:50%;" | IntStream '''codePoints'''() 8  | |||
| style="width:50%;" | 产生由当前字符串的所有 Unicode 码点构成的流。  | |||
|-  | |||
! colspan="2"  style="text-align:left;"| java.util.Random 1.0  | |||
|-  | |||
| style="width:50%;" | IntStream ints ()  | |||
| style="width:50%;" rowspan="12" | 产生随机数流。  | |||
* 如果提供了streamSize, 这个流就是具有给定数址元素的有限流。  | |||
* 当提供了边界时,其元素将位于 randomNumberOrigin (包含)和 randomNumberBound(不包含)的区间内。  | |||
|-  | |||
| style="width:50%;" | IntStream ints(int randomNumberOrigin, int randomNumberBound) 8  | |||
|-  | |||
| style="width:50%;" | IntStream ints(long streamSize) 8  | |||
|-  | |||
| style="width:50%;" | IntStream ints(long streamSize, int randomNumberOrigin, int randomNumberBound) 8  | |||
|-  | |||
| style="width:50%;" | LongStream longs() 8  | |||
|-  | |||
| style="width:50%;" | LongStream longs(long randomNumberOrigin, long randomNumberBound) 8  | |||
|-  | |||
| style="width:50%;" | LongStream longs(long streamSize) 8  | |||
|-  | |||
| style="width:50%;" | LongStream 1ongs(long streamSize, long randomNumberOrigin, long randomNumberBound) 8  | |||
|-  | |||
| style="width:50%;" | DoubleStream doubles() 8  | |||
|-  | |||
| style="width:50%;" | DoubleStream doubles(double randomNumberOrigin, double randomNumberBound) 8  | |||
|-  | |||
| style="width:50%;" | DoubleStream doubles(long streamSize) 8  | |||
|-  | |||
| style="width:50%;" | DoubleStream doubles(long streamSize, double randomNumberOrigin, double randomNumberBound) 8  | |||
|-  | |||
! colspan="2"  style="text-align:left;"| java.util.Optional(Int | Long | Double) 8  | |||
|-  | |||
| style="width:50%;" | static Optional(Int | Long | Double) '''of'''((int | long | double) value)  | |||
| style="width:50%;" | 用所提供的基本类型值产生一个可选对象。  | |||
|-  | |||
| style="width:50%;" | (int | long | double) '''getAs'''(Int | Long | Double)()  | |||
| style="width:50%;" | 产生当前可选对象的值,或者在其为空时抛出一个 NoSuchElementException 异常。  | |||
|-  | |||
| style="width:50%;" | (int | long | double) '''orElse'''((Int | long | double) other)  | |||
| style="width:50%;" rowspan="2" | 产生当前可选对象的值,或者在这个对象为空时产生可替代的值。  | |||
|-  | |||
| style="width:50%;" | (int | long | double) '''orElseGet'''((Int | Long | Double)Supplier other)  | |||
|-  | |||
| style="width:50%;" | void '''ifPresent'''((Int | Long | Double)Consumer consumer)  | |||
| style="width:50%;" | 如果当前可选对象不为空,则将其值传递给 consumer。  | |||
|-  | |||
! colspan="2"  style="text-align:left;"| java.util.(Int | Long | Double)SummaryStatistics 8  | |||
|-  | |||
| style="width:50%;" | long '''getCount'''()  | |||
| style="width:50%;" rowspan="5" | 产生收集到的元素的个数、总和、平均值、最大值和最小值。  | |||
|-  | |||
| style="width:50%;" | (int | long | double) '''getSum'''()  | |||
|-  | |||
| style="width:50%;" | double '''getAverage'''()  | |||
|-  | |||
| style="width:50%;" | (int | long | double) '''getMax'''()  | |||
|-  | |||
| style="width:50%;" | (int | long | double) '''getMin'''()  | |||
|}  | |||
=== 并行流 ===  | === 并行流 ===  | ||
并行流的获取:  | 并行流的获取:  | ||
#   | # '''从任何集合中获取一个并行流''':“Collection.parallelStream()”方法:  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
Stream<String> parallelWords = words.parallelStream();  | Stream<String> parallelWords = words.parallelStream();  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
#   | # '''将任意的顺序流转换为并行流''':“parallel”方法:  | ||
#: <syntaxhighlight lang="java">  | #: <syntaxhighlight lang="java">  | ||
Stream<String> parallelWords = Stream.of(wordArray).parallel();  | Stream<String> parallelWords = Stream.of(wordArray).parallel();  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
并行流的注意事项:  | |||
* '''只要在终结方法执行时,流处于并行模式,那么所有的中间流操作都将被并行化。'''  | |||
* 当流操作并行运行时,'''这些操作必须可以以任意顺序执行''',才能使其返回结果与顺序执行时返回的结果相同。  | |||
*: 示例:对字符串流中所有短单词计数:  | |||
*: <syntaxhighlight lang="java">  | |||
<syntaxhighlight lang="java">  | // 错误的方式:  | ||
// 传递给 forEach 的函数会在多个并发线程中运行  | |||
// 由于每个都会更新共享的数组,所以多次运行这个程序时,很可能出现每次运行都会产生不同的计数值,而且每个都是错的。  | |||
int[] shortWords = new int[12];  | int[] shortWords = new int[12];  | ||
words.para11e1Stream().forEach(  | words.para11e1Stream().forEach(  | ||
    s -> { if (s.length() < 12) shortWords[s.length()]++; });   // Error-race condition!  |     s -> { if (s.length() < 12) shortWords[s.length()]++; });   // Error-race condition!  | ||
System.out.print1n(Arrays.toString(shortWords));  | System.out.print1n(Arrays.toString(shortWords));  | ||
/  | </syntaxhighlight>  | ||
//   | *: 如上:这是一种非常非常糟糕的代码。。如果  | ||
*: <syntaxhighlight lang="java">  | |||
//   | // 推荐的方式:  | ||
// 以字符串长度将其分为群组,然后分别计数  | |||
Map<lnteger, long> shortWordCounts =  | Map<lnteger, long> shortWordCounts =  | ||
    words.parallelStream()  |     words.parallelStream()  | ||
| 第924行: | 第1,029行: | ||
          counting()));  |           counting()));  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
*   | * '''传递给并行流操作的函数不应该被堵塞''':并行流使用 fork-join 池来操作流的各个部分。如果多个流操作被阻塞,那么池可能就无法做任何事情了。  | ||
** 参见:<big><span style="color: green">'''[[Fork-Join框架]]'''</span></big>  | |||
*   | |||
 '''默认情况下,从有序集合(数组和列表)、范围、生成器和迭代产生的流,或者通过调用 Stream.sorted 产生的流,都是有序的。'''  | |||
 它们的结果是按照原来元素的顺序累积的,因此是完全可预知的。如果运行相同的操作两次,将会得到完全相同的结果。  | |||
<  | * 排序并不排斥高效的并行处理。  | ||
*: 例如,当计算 stream.map(fun)时,流可以被划分为 n 的部分,它们会被并行地处理。然后,结果将会按照顺序重新组装起来。  | |||
</  | * 当放弃排序需求时(在流上调用“'''unordered'''”方法),有些操作可以被更有效地并行化。  | ||
<syntaxhighlight lang="java">  | *: 在有序的流中,“'''distinct'''”会保留所有相同元素中的第一个,这对并行化是一种阻碍,因为处理每个部分的线程在其之前的所有部分都被处理完之前,并不知道应该丢弃哪些元素。如果可以接受保留唯一元素中任意一个的做法,那么所有部分就可以并行地处理(使用共享的集来跟踪重复元素)。  | ||
Map<  | * 还可以通过放弃排序要求来提高 limit 方法的速度。  | ||
*: 例如:如果只想从流中取出任意 n 个元素,而并不在意到底要获取哪些,那么可以调用:  | |||
*: <syntaxhighlight lang="Java" highlight="">  | |||
Stream<String> sample = words.parallelStream().unordered().limit(n);  | |||
</syntaxhighlight>  | |||
* 由于合并映射表的代价很高昂,“Collectors.'''groupByConcurrent'''”方法使用了共享的'''并发映射表'''。为了从并行化中获益,'''映射表中值的顺序不会与流中的顺序相同'''。  | |||
*: <syntaxhighlight lang="java">  | |||
Map<Integer, List<String>> result = words.paral1e1Stream().collect(  | |||
    Col1ectors.groupingByConcurrent(String::1ength));  |     Col1ectors.groupingByConcurrent(String::1ength));  | ||
// Values aren't collected in stream order  | // Values aren't collected in stream order  | ||
</syntaxhighlight>  | </syntaxhighlight>  | ||
当然,如果使用独立于排序的下游收集器,那么就不必在意了:  | *: 当然,如果使用独立于排序的下游收集器,那么就不必在意了:  | ||
<syntaxhighlight lang="java">  | *: <syntaxhighlight lang="java">  | ||
Map<Integer, Long> wordCounts =  | Map<Integer, Long> wordCounts =  | ||
    words.parallelStream()  |     words.parallelStream()  | ||
| 第949行: | 第1,063行: | ||
 '''不要修改在执行某项流操作后会将元素返回到流中的集合(即使这种修改是线程安全的)。'''  | |||
    1、流并不会收集它们的数据,数据总是在单独的集合中。  | |||
    2、如果修改了这样的集合,那么流操作的结果就是未定义的。  | |||
    3、JDK 文档对这项需求并未做出任何约束,并且对顺序流和并行流都采用了这种处理方式。  | |||
<syntaxhighlight lang="java">  | 更准确地讲,因为中间的流操作都是惰性的,所以直到执行终结操作时才对集合进行修改仍旧是可行的,但仍然不推荐这种修改。  | ||
: 例如,下面的操作尽管并不推荐,但是仍旧可以工作:  | |||
: <syntaxhighlight lang="java">  | |||
// 不推荐的代码  | // 不推荐的代码  | ||
List<String> wordList = ...;  | List<String> wordList = ...;  | ||
| 第961行: | 第1,077行: | ||
wordlist.add("END");  | wordlist.add("END");  | ||
long n = words.distinct().count();  | long n = words.distinct().count();  | ||
</syntaxhighlight>  | |||
: 但是,下面的代码是错误的:  | |||
: <syntaxhighlight lang="java">  | |||
// 错误的代码  | // 错误的代码  | ||
Stream<String> words = wordlist.stream();  | Stream<String> words = wordlist.stream();  | ||
| 第968行: | 第1,086行: | ||
 '''不要将所有的流都转换为并行流。只有在对已经位于内存中的数据执行大量计算操作时,才应该使用并行流。'''  | |||
不要将所有的流都转换为并行流。只有在对已经位于内存中的数据执行大量计算操作时,才应该使用并行流。  | |||
为了让并行流正常工作,需要满足大批的条件:  | 为了让并行流正常工作,需要满足大批的条件:  | ||
#   | # 数据应该在内存中。必须等到数据到达是非常低效的。  | ||
# 流应该可以被高效地分成若干个子部分。  | # 流应该可以被高效地分成若干个子部分。  | ||
#:   | #: 由数组或平衡二叉树支撑的流都可以工作得很好,但是 Stream.iterate 返回的结果不行。  | ||
#   | # 流操作的工作量应该具有较大的规模。  | ||
#: 如果总工作负载并不是很大,那么搭建并行计算时所付出的代价就没有什么意义。  | #: 如果总工作负载并不是很大,那么搭建并行计算时所付出的代价就没有什么意义。  | ||
# 流操作不应该被阻塞。  | # 流操作不应该被阻塞。  | ||
:'''相关方法:'''  | |||
:{| class="wikitable" style="width:100%;"  | |||
|-  | |||
! colspan="2"  style="text-align:left;"| java.util.stream.BaseStream<T, S extends BaseStream<T,S>> 8  | |||
|-  | |||
| style="width:50%;" | S '''parallel'''()  | |||
| style="width:50%;" | 产生一个与当前流中元素相同的并行流。  | |||
|-  | |||
| style="width:50%;" | S '''unordered'''()  | |||
| style="width:50%;" | 产生一个与当前流中元素相同的无序流。  | |||
|-  | |||
! colspan="2"  style="text-align:left;"| iava.util.Collection<E> 1.2  | |||
|-  | |||
| style="width:50%;" | Stream<E> '''parallelStream'''() 8  | |||
| style="width:50%;" | 用当前集合中的元素产生一个并行流。  | |||
|}  | |||
2022年4月22日 (五) 19:19的最新版本
关于 Java SE 8 的流库
Stream它并不是一个容器,它只是对容器的功能进行了增强,添加了很多便利的操作,例如查找、过滤、分组、排序等一系列的操作。 并且有串行、并行两种执行模式,并行模式充分的利用了多核处理器的优势,使用fork/join框架进行了任务拆分,同时提高了执行速度。 简而言之,Stream就是提供了一种高效且易于使用的处理数据的方式。
流提供了一种在比集合更高的概念级别上指定计算的数据视图,以“做什么而非怎么做”的方式处理集合。
从迭代到流的操作
处理集合时,通常会迭代遍历它的元素,并在每个元素上执行某项操作:
String contents = new String(Files.readAllBytes(Paths.get("alice.txt")), Standard(harsets,UTF_8); // Read file into string
List<String> words= Arrays.aslist(contents.split("\\PL+")); // Split into words; nonletters are delimiters
long count = O;
for (String w : words)
{
   if (w. length() > 12) 
      count++;
}
使用流时,相同的操作看起来像下面这样:
1ong count = words.stream().filter(w -> w.length() > 12).count();
- 仅将stream 修改为parallelStream 就可以让流库以并行方式来执行过滤和计数:
1ong count = words.parallelStream().filter(w -> w.length() > 12).count();
 
以上:
- stream 和parallel Stream 方法会产生一个用于words 列表的stream。
 - filter 方法会返回另一个流,其中只包含长度大于12 的单词。
 - count 方法会将这个流化简为一个结果。
 
流与集合
流表面上活起来和集合很类似,都可以让我们转换和获取数据。但是,它们之间存在着显著的差异:
- 流并不存储其元素。
- 这些元素可能存储在底层的栠合中,或者是按需生成的。
 
 - 流的操作不会修改其数据源。
- 例如,filter 方法不会从新的流中移除元素,而是会生成一个新的流,其中不包含被过滤掉的元素。
 
 - 流的操作是尽可能惰性执行的。这意味若直至需要其结果时,操作才会执行。
- 例如,如果我们只想查找前5 个长单词而不是所有长单词,那么filter 方法就会在匹配到第5 个单词后停止过滤。因此,我们甚至可以操作无限流。
 
 
- 相关方法:
 
java.util.stream.Stream<T> 8 Stream<T> filter(Predicate<? super T> p) 产生一个流,其中包含当前流中满足P 的所有元索。 long count() 产生当前流中元素的数批。这是一个终止操作。 java.util.Collection<E> 1.2 default Stream<E> stream() 产生当前集合中所有元素的顺序流或并行流。 default Stream<E> parallelStream() 
流的创建
- 集合转换为流:
- 用“Collection.stream()”方法将任何集合转换为一个流。
 
 - 数组转换为流:
- 使用“Array.stream(array, from, to)”可以从数组中位于 from (包括)和 to (不包括)的元索中创建一个流。
 - 使用 Stream 静态的“of”方法,将数组转换为流:
Stream<String> words= Stream.of(contents.split("\\PL+")); // split returns a String[] array
- of 方法具有【可变长参数】,因此我们可以构建具有任意数值引元的流:
Stream<String> song = Stream.of("gently", "down", "the", "stream");
 
 
 - 空流:
- 使用静态的 Stream.empty 方法,创建不包含任何元素的流;
 Stream<String> silence = Stream.empty(); // Generic type <String> is inferred; same as St ream. <St ri ng>empty()
 - 无限流:
- generate 方法:接受一个不包含任何引元的函数(或者从技术上讲,是一个 Supplier<T> 接口的对象)。
Stream<String> echos = Stream.generate(() -> "Echo"); // 获得一个常批值的流: Stream<Double> randoms = Stream.generate(Math:: random); // 获得一个随机数的流:
 - iterate 方法:接受一个“种子”值,以及一个函数(从技术上讲,是一个UnaryOperation<T>), 并且会反复地将该函数应用到之前的结果上。
Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE)); // 第一个元素是种子Biglnteger.ZERO, 第二个元素是f(seed), 即l (作为大整数),下一个元素是f(f(seed)), 即2, 后续以此类推。
 
 - generate 方法:接受一个不包含任何引元的函数(或者从技术上讲,是一个 Supplier<T> 接口的对象)。
 
Java API中的其他流方法:
- Pattern 类有一个“splitAsStream”方法,它会按照某个正则表达式来分割一个“CharSequence”对象:
Stream<String> words = Pattern.compile("\\PL+").splitAsStream(contents);
 - 静态的“Files.lines()”方法会返回一个包含了文件中所有行的 Stream:
try (Stream<String> lines = Files.lines(path)) { ...Process lines... }
 
- 相关方法:
 
java.util.stream.Stream 8 static <T> Stream<T> of(T .. . values) 产生一个元素为给定值的流。 static <T> Stream<T> empty() 产生一个不包含任何元素的流。 static <T> Stream<T> generate(Supplier<T> s) 产生一个无限流,它的值是通过反复调用函数s 而构建的。 static <T> Stream<T> iterate(T seed , UnaryOperator<T> f ) 产生一个无限流,它的元素包含种子、在种子上调用f 产生的值、在前一个元素上调用f 产生的值,等等。 java.util.Arrays 1.2 static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) 8 产生一个流,它的元素是由数组中指定范围内的元素构成的。 java.util.regex.Pattem 1.4 Stream<String> splitAsStream(CharSequence input) 8 产生一个流,它的元素是输入中由该模式界定的部分。 java.nio.file.Files 7 static Stream<String> lines(Path path) 8 产生一个流,它的元素是指定文件中的行,该文件的字符集为 UTF-8,或者为指定的字符集。 static Stream<String> lines(Path path, Charset cs) 8 java.util.function.Supplier<T> 8 T get() 提供一个值。 
流的转换
流的转换会产生一个新的流,它的元素派生自另一个流中的元素。
filter 、map 和 flatMap 方法
- filter 转换会产生一个流,它的元素与某种条件相匹配。
- filter 的引元是 Predicate<T>, 即从 T 到 boolean 的函数。
 List<String> wordlist = . . . ; Stream<String> longwords = wordlist.stream().filter(w -> w.length() > 12);
 - map 方法用于按照某种方式来转换流中的值。
// 1、使用函数式接口: Stream<String> lowercaseWords = words.stream().map(String::tolowerCase); // 2、或者使用lambda替换: Stream<String> firstletters = words.stream().map(s -> s.substring(O, 1));
 - flatMap 方法用于“摊平由流构成的流”。
- (即,将包含流的流,变为流)
 // 1、map Stream<Stream<String>> result = words.stream().map(w -> letters(w)); // 2、flatMap Stream<String> flatResult = words.stream().flatMap(w -> letters(w)); letters: public static Stream<String> letters(String s) { List<String> result = new ArrayList<>(); for (int i = O; i < s.length(); i++) result.add(s.substring(i, i + 1)); return result.stream(); } // 若 w 为“[[your][boat]]”; // 1、结果为“[...["y","o", "u" ,"r"],["b" ,"o" ,"a" ,"t"],...]”; // 2、结果为“[..."y","o","u" ,"r", "b","o" ,"a" ,"t",...],”;
 
- 相关方法:
 
java.util.stream.Stream 8 Stream<T> filter(Predicate<? super T> predicate) 产生一个流,它包含当前流中所有满足断言条件的元索。 <R> Stream<R> map(Function<? super T, ? extends R> mapper) 产生一个流,它包含将 mapper 应用于当前流中所有元素所产生的结果。 <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) 产生一个流,它是通过将 mapper 应用于当前流中所有元素所产生的结果连接到一起而获得的。(注意,这里的每个结果都是一个流。) 
抽取子流和连接流
- stream.limit(n):会返回一个新的流,它在n个元素之后结束(如果原来的流更短,那么就会在流结束时结束)。
- (对于裁剪无限流的尺寸特别有用。)
 Stream<Doub1e> randoms = Stream.generate(Math::random).1imit(lOO); // 会产生一个包含100 个随机数的流
 - stream.skip(n):会丢弃前n个元素。
- (在将文本分隔为单词时会显得很方便。)
 Stream<String> words = Stream.of(contents.split("\\PL+")).skip(l); // 会跳过split方法产生字符串的第一个字符(空字符串)
 - Stream.concat(Stream<? extends T> a, Stream<? extends T> b):静态方法,用于拼接两个流。
- 第一个流不应该是无限的,否则第二个流没有处理机会。
 
Stream<String> combined= Stream.concat(1etters("Hello"), 1etters("Wor1d")); // Yields the stream ["H", "e", "l", "1", "o", "W", "o", "r", "l", "d")
 
- 相关方法:
 
java:util.stream.Stream 8 Stream<T> limit(long maxSize) 产生一个流,其中包含了当前流中最初的 maxSize 个元素。 Stream<T> skip(long n) 产生一个流,它的元素是当前流中除了前 n 个元素之外的所有元素。 static<T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) 产生一个流,它的元素是 a 的元素后面跟着 b 的元素。 
其他的流转换
- distinct:方法会返回一个流,它的元素是从原有流中,按照同样的顺序剔除重复元素后产生的。
Stream<String> uniqueWords = Stream.of("merrily", "merrily", "merrily", "gently").distinct(); // Only one "merrily" is retained
 - sorted:方法会返回一个流,它的元素是原有流中按照顺序排列的元素。
- (有多种sorted方法的变体可用:一种用于操作“Comparable”元素的流,另一种可以接受一个“Comparator”)
 Stream<String> longestFirst = words.stream().sorted(Comparator.comparing(String::1ength).reversed());
 - peek:元素与原来流中的元索相同,但是在每次获取一个元素时,都会调用一个函数。
- (对于调试可以让peek调用一个设置断点的方法。)
 Object[] powers= Stream.iterate(l.O, p -> p * 2).peek(e -> System.out.println("Fetching" + e)).1imit(20).toArray(); // 当实际访问一个元素时,就会打印出来一条消息。
 
- 相关方法:
 
java.util.stream.Stream 8 Stream<T> distinct() 产生一个流,包含当前流中所有不同的元素。 Stream<T> sorted() 产生一个流,它的元素是当前流中的所有元素按照顺序排列的。 第一个方法要求元素是实现了 Comparable 的类的实例。
Stream<T> sorted(Comparator<? super T> comparator) Stream<T> peek(Consumer<? super T> action) 产生一个流,它与当前流中的元素相同,在获取其中每个元素时,会将其传递给action。 
流的终止操作
流的终止操作:即从流数据中获得答案。
简单约简
约简是一种终结操作(tennjnal operation),它们会将流约简为可以在程序中使用的非流值。
- 这些方法返回的是一个类型“Optional<T>”的值,它要么在其中包装了答案,要么表示没有任何值(因为流碰巧为空)。
 
常用的简单约简:
- “count”方法,会返回流中元素的数量;
 - “max”、“min”方法,会返回流中元素的最大值和最小值;
Optional<String> largest = words.max(String::compareToIgnoreCase); System.out.println("largest: " + largest.orElse(""));
 - “findFirst”方法,返回的是非空集合中的第一个值;
- (通常会在与filter 组合使用时显得很有用)
 Optional<String> startsWithQ = words.filter(s -> s.startsWith("Q")).findFirst(); // 找到第一个以字母Q 开头的单词
 - “findAny”方法,返回的是非空集合中的任意匹配值;
Optional<String> startsWithQ = words.parallel().filter(s -> s.startsWith("Q")).findAny();
 - “anyMatch”方法,只返回是否存在匹配;
- (这个方法会接受一个断言引元Predicate,因此不需要使用filter)
 boolean aWordStartsWithQ = words.parallel().anyMatch(s -> s.startsWith("Q"));
 - “allMatch”方法,在所有元素匹配断言的情况下返回 true;
 - “noneMatch”方法,在没有任何元素匹配断言的情况下返回 true;
 
Optional 类型
Optional<T> 对象是一种包装器对象,要么包装了类型 T 的对象,要么没有包装任何对象。
- Optional<T> 类型被当作一种更安全的方式,用来替代类型 T 的引用,这种引用要么引用某个对象,要么为 null。
 
如何使用 Optional 值
使用 Optional 的关键:
- 在值不存在的情况下会产生一个可替代物:
// 在没有任何匹配时:使用某种默认值,可能是空字符串: String result= optionalString.orElse('"'); // The wrapped string, or "" if none // 调用代码来计算默认值: String result= optionalString.orElseGet(() -> Locale.getDefault().getDisplayName()); // The function is only called when needed // 在没有任何值时抛出异常: String resu1t = optiona1String.orElseThrow(Illega1StateException::new); // Supply a method that yie1ds an exception object
 - 在值存在的情况下才会使用这个值:
- “ifPresent”方法:接受一个函数。如果该可选值存在,那么它会被传递给该函数。否则不会发生任何事情。
 
- (当调用 ifPresent 时,从该函数不会返回任何值。如果想要处理函数的结果,应该使用 map)
 
// 1、使用 ifPresent 来调用函数处理 optionalValue optionalValue.ifPresent(v -> Process v); // 调用Process处理v // 2、将其添加到某个集中 optionalValue.ifPresent(v -> results.add(v)); optionalValue.ifPresent(results::add); // 或使用函数式接口 // 当调用 ifPresent 时,从该函数不会返回任何值。如果想要处理函数的结果,应该使用 map: Optional<Boo1ean> added = optianalVa1ue.map(results::add); // 现在added 具有三种值之一: // 在optionalValue存在的情况下包装在Optional中的true或false, 以及在optionalValue不存在的情况下的空Optional。
 
- 相关方法:
 
java.util.Optional 8 T orElse(T other) 产生这个 Optional 的值,或者在该 Optional 为空时,产生 other。 T orElseGet(Supplier<? extends T> other) 产生这个 Optional 的值,或者在该 Optional 为空时,产生调用 other 的结果。 <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) 产生这个 Optional 的值,或者在该 Optional 为空时,抛出调用 exceptionSupplier 的结果。 void ifPresent(Consumer<? super T> consumer) 如果该 Optional 不为空,那么就将它的值传递给 consumer 。 <U> Optional<U> map(Function<? super T , ? extends U> mapper) 产生将该 Optional 的值传递给 mapper 后的结果,只要这个 Optional 不为空且结果不为 null , 否则产生一个空 Optional。 
不适合使用 Optional 值的方式
如果没有正确地使用 Optional 值,那么相比较以往的得到“某物或null”的方式,你并没有得到任何好处。
- get 方法会在 Optional 值存在的情况下获得其中包装的元素,或者在不存在的情况下抛出一个 NoSuchElementException 对象。
- 因此,
 Optional<T> optionalValue = ...; optionalValue.get().someMethod();
- 并不比下面的方式更安全:
 T value = ...; va1ue.someMethod();
 - isPresent 方法会报告某个 Optional<T> 对象是否具有一个值。
- 但是,
 if(optionalVa1ue.isPresent()) optionalValue.get().someMethod();
- 并不比下面的方式更容易处理:
 if (value != null) value.someMethod();
 
- 相关方法:
 
java.util.Optional 8 T get() 产生这个 Optional 的值,或者在该 Optional 为空时, 抛出一个 NoSuchElementException 对象。 boolean isPresent() 如果该 Optional 不为空,则返回 true。 
创建 Optional 值
有多个方法可以创建 Optional 对象:
- “Optional.of(result)”
 - “Optional.empty()”
 - “Optional.ofNullable(obj)”:在 obj 不为 null 的情况下返回“Optional.of(obj)”, 否则会返回“Optional.empty()”。
public static Optional<Double> inverse(Double x) { return x = 0 ? Optional.empty() : Optional.of(l / x); }
 
- 相关方法:
 
java.util.Optional 8 static <T> Optional<T> of(T value) 产生一个具有给定值的 Optional。 - 如果 value 为 nul1, 那么第一个方法会抛出一个 NullPointerException 对象, 而第二个方法会产生一个空 Optional。
 
static <T> Optional<T> ofNullable(T value) static <T> Optional<T> empty() 产生一个空 Optional。 
用 flatMap 来构建 Optional 值的函数
- (类比于“stream.flatMap()”)
 
“Optional.flatMap()”用来将流计算过程中的方法连接起来:
假设你有一个可以产生 Optional<T> 对象的方法 f, 并且目标类型 T 具有一个可以产生 Optional<U> 对象的方法 g。如果它们都是普通的方法,那么你可以通过调用“s.f().g()”来将它们组合起来。 但是这种组合没法工作,因为“s.f()”的类型为 Optional<T>, 而不是 T。因此,需要调用: Optional<U> result= s.f().flatMap(T::g); 如果 s.f() 的值存在,那么 g 就可以应用到它上面。否则,就会返回一个空 Optional<U> 。 很明显,如果有更多的可以产生 Optional 值的方法或 Lambda 表达式,那么就可以重复此过程。
可以直接将对 flatMap 的调用链接起来,从而构建由这些步骤构成的管道,只有所有步骤都成功时,该管道才会成功。
public static Optional<Double> squareRoot(Double x)
{
   return x<0 ? Optional.empty() : Optional.of(Math.sqrt(x));
}
// 计算倒数的平方根:
Optional<Double> result = inverse(x).flatMap(MyMath::squareRoot);
// 或者,你可以选择下面的方式:
Optional<Double> result = Optional.of(-4.0).flatMap(MyMath::inverse).flatMap(MyMath::squareRoot);
- 相关方法:
 
java.util.Optional 8 <U> Optional<U> flatMap(Function<? super T,Optional> mapper) 产生将 mapper 应用于当前的 Optional 值所产生的结果,或者在当前 Optional 为空时,返回一个空 Optional。 
收集结果
当处理完流之后,通常会想要查看、收集其元素。
查看元素
- 调用“iterator”方法,它会产生可以用来访问元素的旧式风格的迭代器。
 - 调用“forEach”方法,将某个函数应用于每个元素:
- 在并行流上,forEach会以任意顺序遍历各个元索;
 - 如果想要按照流中的顺序来处理它们,可以调用“forEachOrdered”方法(会丧失并行处理的部分甚至全部优势);
 
stream.forEach(System.out::println);
 
- 相关方法:
 
java.util.stream.BaseStream 8 Iterator<T> iterator( ) 产生一个用于获取当前流中各个元素的迭代器。 - 这是一种终结操作。
 
java.util.stream.Stream 8 void forEach(Consumer<? super T> action) 在流的每个元素上砌用 action。 - 这是一种终结操作。
 
收集元素到集合(Collection)
- “stream.toArray()”:收集到数组中:
- (如果想要让数组具有正确的类型,可以将其传递到数组构造器中)
 
// 1、“stream.toArray()”:会返回一个Object[]数组; String[] result = stream.toArray(); // 2、使用构造器,使数组有正确的类型 String[] result = stream.toArray(String[]::new); // stream.toArray() has type Object[]
 - “stream.collect()”:收集到其他目标中;
- 该方法接受—个“Collector”接口的实例(Collectors类提供了大量用于生成公共收集器的工厂方法);
 
- 收集到列表或集:
List<String> resu1t = stream.co11ect(Co11ectors.toList()); 或 Set<String> result = stream.collect(Collectors.toSet());
 - 控制获得的集的种类:
TreeSet<String> result = stream.collect(Co11ectors.toCo11ection(TreeSet::new)) ;
 - 通过连接操作来收集流中的所有字符串:
String result= stream.collect(Collectors.joining());
 - 在元素之间增加分隔符,可以将分隔符传递给joining 方法:
String result= stream.collect(Collectors.joining(", "));
 - 如果流中包含除字符串以外的其他对象,那么我们需要现将其转换为字符串:
String result = stream.map(Object::toString).collect(Collectors.joining(", "));
 - 如果想要将流的结果约简为总和、平均值、最大值或最小值,可以使用“summarizing(Int | Long | Double)”方法中的某一个。
- (这些方法会接受一个将流对象映射为数据的函数,同时,这些方法会产生类型为“(Int | Long | Double)SummaryStatistics”的结果,同时计算总和、数最、平均值、最小值和最大值。)
 
IntSummaryStatistics summary = stream.co11ect(Co11ectors.summarizingInt(String::length)); double averageWordLength = summary.getAverage(); double maxWordLength = summary.getMax();
 
 
- 相关方法:
 
java.util.stream.BaseStream 8 Object[] toArray() 产生一个对象数组,或者在将引用A[]::new 传递给构造器时,返回一个A 类型的数组。 - 这些操作都是终结操作。
 
<A> A[] toArray(IntFunction<A[]> generator) <R, A> R collect(Collector<? super T,A,R> collector) 使用给定的收集器来收集当前流中的元素。 - Collectors 类有用于多种收集器的工厂方法。
 
java.util.stream.Collectors 8 static <T> Collector<T, ?, List<T>> toList() 产生一个将元素收集到列表或集中的收集器。 static <T> Collector<T, ? , Set<T>> toSet() static <T, C extends Collection<T>> Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) 产生一个将元素收集到任意集合中的收集器。可以传递一个诸如TreeSet::new 的构造器引用。 static Collector<CharSequence, ?, String> joining() 产生一个连接字符串的收集器。 - 分隔符会翌于字符串之间,而第一个字符串之前可以有前缀,最后一个字符串之后可以有后缀。
 - 如果没有指定分隔符,那么它们都为空。
 
static Collector<CharSequence, ?, String> joining(CharSequence delimiter) static Collector<CharSequence, ?, String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) static <T> Collector<T, ?, IntSumnaryStatisties> sumnarizingInt(ToIntFunction<? super T> mapper) 产生能够生成 (Int | Long | Double)SummaryStatistics 对象的收集器,通过它可以获得将 mapper 应用于每个元素后所产生的结果的个数、总和、平均值、最大值和最小值。 static <T> Collector<T, ?, LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper) static <T> Collector<T, ?, DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper) IntSummaryStatistics 8 LongSummaryStatistics 8 DoubleSummaryStatistics 8 long getCount () 产生汇总后的元素的个数。 (int | long | double) getSum() 产生汇总后的元素的总和或平均值,或者在没有任何元素时返回 0。 double getAverage() (int | long | double) getMax () 产生汇总后的元素的最大值和最小值,或者在没有任何元素时,产生 (Integer | Long | Double).(MAX | MIN)_VALUE。 (int | long | double) getMin() 
收集到映射表(Map)
“Collectors.toMap”方法有两个函数引元,它们用来产生映射表的键和值。
Map<Integer, String> idToName = people.collect(Co11ectors.toMap(Person::getId, Person::getName));
- 通常情况下,值应该是实际的元素,因此第二个函数可以使用“Function.identity()”;
- (“Function.identity()”:返回一个输出跟输入一样的 Lambda 表达式对象,等价于形如“t -> t”形式的 Lambda 表达式)
 Map<Integer, String> idToPerson = peop1e.collect(Collectors.toMap(Person::getId, Function.identity()));
 - 如果有多个元素具有相同的键,那么就会存在冲突,收集器将会抛出一个“IllegalStateException”对象。
- 可以通过提供第 3 个函数引元来覆盖这种行为:
 Stream<Locale> loca1es = Stream.of(Locale.getAvailablelocales()); Map<String, String> languageNames = locales.collect( Co11ectors.toMap( Loca1e::getDisplaylanguage, l -> l.getDisplaylanguage(l), (existingValue, newValue) -> existingValue));
 - 如果想要得到 TreeMap, 那么可以将构造器作为第 4 个引元来提供(合并函数):
Map<lnteger, Person> idToPerson = people.collect( Co11ectors.toMap( Person::getld , Function.identity(), (existingValue, newValue) -> { throw new IllegalStateException(); }, TreeMap::new));
 
对于每一个 toMap 方法,都有一个等价的可以产生并发映射表的“toConcurrentMap”方法。【???】
- 单个并发映射表可以用于并行集合处理。
 - 当使用并行流时,共享的映射表比合并映射表要更高效。
 - 元素不再是按照流中的顺序收集的(但是通常这不会有什么问题)。
 
- 相关方法:
 
java.util.stream.Collector 8 static <T, K, U> Collector<T, ?, Map<K, U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) 产生一个收集器,它会产生一个映射表或并发映射表。 - keyMapper 和valueMapper 函数会应用于每个收集到的元素上,从而在所产生的映射表中生成一个键/值项。
 - 默认情况下,当两个元素产生相同的键时,会抛出一个 IllegalStateException 异常。你可以提供一个mergeFunction 来合并具有相同键的值。
 - 默认情况下,其结果是一个 HashMap 或 ConcurrentHashMap 。你可以提供一个 mapSupplier, 它会产生所期望的映射表实例。
 
static <T, K, U> Collector<T, ?, Map<K, U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction) static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier) static <T, K, U> Collector<T, ?, ConcurrentMap<K, U>> toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) static <T, K, U> Collector<T, ?, ConcurrentMap<K, U>> toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction) static <T, K, U, M extends ConcurrentMap<K, U>> Collector<T, ?, M> toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier) 
群组和分区
- “groupingBy”方法,将具有相同特性的值群聚成组;(群组)
Map<String, List<Locale>> countryTolocales = 1oca1es.collect(Co11ectors.groupingBy(loca1e::getCountry));
 - “partitioningBy”方法;(分区)
- 当分类函数是断言函数(即返回 boolean 值的函数)时,流的元素可以分区为两个列表:该函数返回 true 的元素和其他的元素。在这种情况下,使用 partitioningBy 比使用 groupingBy 要更高效。
 Map<Boolean, List<locale>> englishAndOtherlocales = locales.collect(Collectors.partitioningBy(l -> l.getlanguage().equals("en"))); List<locale> englishloca1es = englishAndOtherlocales.get(true);
 
- 如果调用 groupingByConcurrent 方法,就会在使用并行流时获得一个被并行组装的并行映射表。这与 toConcurrentMap 方法完全类似。
 
- 相关方法:
 
java.util.stream.Collector 8 static <T, K> Collector<T, ?, Map<K,List<T>>> groupingBy(Function<? super T, ? extends K> classifier) 产生一个收集器,它会产生一个映射表或并发映射表,其键是将 classifier 应用于所有收集到的元素上所产生的结果,而值是由具有相同键的元素构成的一个个列表。 static <T, K> Collector<T, ?, ConcurrentMap<K, List<T>>> groupingByConcurrent(Function<? super T, ? extends K> classifier) static <T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) 产生一个收集器,它会产生一个映射表,其键是 true/false , 而值是由 满足/不满足 断言的元素构成的列表。 
下游收集器【???】
groupingBy 方法会产生一个映射表(Map),它的每个值都是一个列表(List)。如果想要以某种方式来处理这些列表,就需要提供一个“下游收集器”。
- 例如,如果想耍获得集(Set)而不是列表(List),那么可以使用上一节中看到的“Collector.toSet”收集器:
 Map<String, Set<Locale>> countrylolocaleSet = locales.collect(groupingBy(Locale::getCountry, toSet()));
Java 提供了多种可以将群组元素约简为数字的收集器:(方法均为“java.util.stream.Collectors.xxx”)
- “counting”会产生收集到的元素的个数:
Map<String, Long> countryToLocaleCounts = locales.co11ect( groupingBy(Locale::getCountry, counting())); // 可以对每个国家有多少个 Locale 进行计数。
 - “summing(Int | Long | Double)”会接受一个函数作为引元,将该函数应用到下游元素中,并产生它们的和:
Map<String, Integer> stateToCityPopulation = cities.collect( groupingBy(City::getState, summingInt(City::getPopulation))); // 可以计算城市流中每个州的人口总和。
 - “maxBy”和“minBy”会接受一个比较器,并产生下游元素中的最大值和最小值。例如:
Map<String, Optional<City>> stateToLargestCity = cities.co11ect( groupingBy(City::getState, maxBy(Comparator.comparing(City::getPopulation)))); // 可以产生每个州中最大的城市。
 - “mapping”方法会产生将函数应用到下游结果上的收集器, 并将函数值传递给另一个收集器:
Map<String, Optional<String>> stateToLongestCityName = cities.collect( groupingBy(City::getState, mapping(City::getName, maxBy(Comparator.comparing(String::1ength)))));
 
- 将收集器组合起来是一种很强大的方式,但是它也可能会导致产生非常复杂的表达式。
- 它们的最佳用法是与 groupingBy 和 partitioningBy 一起处理“下游的”映射表中的值。
 - 否则,应该直接在流上应用诸如 map、reduce、count、max 或 min 这样的方法。
 
 
- 相关方法:
 
java.util.stream.Collectors 8 static <T> Collector<T, ?, Long> counting() 产生一个可以对收集到的元素进行计数的收集器。 static <T> Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper) 产生一个收集器,对将 mapper 应用到收集到的元素上之后产生的值计算总和。 static <T> Collector<T, ?, Long> summingLong(ToLongFunction<? super T> mapper) static <T> Col1ector<T, ?, Double> summingDouble(ToDoubleFunction<? super T> mapper) static <T> Collector<T, ?, Optiona1<T>> maxBy(Comparator<? super T> comparator) 产生一个收集器,使用 comparator 指定的排序方法,计算收集到的元素中的最大值和最小值。 static <T> Collector<T, ?, Optional<T>> minBy(Comparator<? super T> comparator) static <T, U, A, R> Collector<T , ? , R> mapping(Function<? super T, ? extends U> mapper, Collector<? super U, A, R> downstream) 产生一个收集器,它会产生一个映射表,其键是将 mapper 应用到收集到的数据上而产生的,其值是使用 downstream 收集器收集到的具有相同键的元素。 
其他
约简操作
- “reduce”方法:是一种用于从流中计算某个值的通用机制,其最简单的形式将接受一个二元函数,并从前两个元素开始待续应用它。
- 如果 reduce 方法有一项约简操作 op,那么该约简就会产生“v0 op v1 op v2 op ...”,其中我们将函数调用“op(vi,vi+1)”写作“vi op vi+1”。
 - 操作应该是可结合的:即组合元素时使用的顺序不应该成为问题【在数学标记法中,(x op y) op z 必须等于 x op (y op z)。即:操作顺序可变,但元素顺序不可变】。这使得在使用并行流时,可以执行高效的约简。
 - 减法是一个不可结合操作的例子,例如,(6-3)-2 != 6-(3-2) 。
 
List<lnteger> values = ... ; Optional<lnteger> sum = values.stream().reduce((x, y) -> x + y); // 或“reduce(Integer::sum)”
 - 通常,会有一个幺元值“e”:使得“e op x = x”,可以使用这个元素作为计算的起点:
- 如果流为空,则会返回幺元值,就再也不需要处理 Optional 类了。
 
List<lnteger> values= ... ; Integer sum = values.stream().reduce(O, (x, y) -> x + y); // Computes O + vo + v1 + v2 + . . .
 - 对于引元和结果的类型相同的函数:需要提供一种“累积器”函数(如:“(total, word) -> total + word.length()”),这个函数会被反复调用,产生累积的总和。
- 但是,当计算被并行化时,会有多个这种类型的计算,需要将它们的结果合并。(因此需要提供第二个函数“组合器”来执行此处理)
 
int result = words.reduce(O, (total, word) -> tota1 + word.length(), (total1, total2) -> totall + total2);
 - 实践中,可能并不会频繁地用到 reduce 方法。通常,映射为数字流并使用其方法来计算总和、最大值和最小值会更容易:
- 因为它不涉及装箱操作,所以更简单也更高效;
 
words.mapToInt(String::length).sum(),
 - 有时 reduce 会显得并不够通用,而应使用“collect”方法:
- 它会接受单个引元:
- 一个提供者,它会创建目标类型的新实例,例如散列集的构造器。
 - 一个累积器,它会将一个元素添加到一个实例上,例如 add 方法。
 - 一个组合器,它会将两个实例合并成一个,例如 addAll。
 
 
BitSet result = stream.co11ect(BitSet::new, BitSet::set, BitSet::or);
 - 它会接受单个引元:
 
- 相关方法:
 
java.util.Stream 8 Optional<T> reduce(BinaryOperator<T> accumulator) 用给定的 accumulator 函数产生流中元素的累积总和。 - 如果提供了幺元,那么第一个被累计的元素就是该么元。
 - 如果提供了组合器,那么它可以用来将分别累积的各个部分整合成总和。
 
T reduce(T identity, BinaryOperator<T> accumulator) <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner) <R> R collect(Supplier<R> supplier, BiConsumer<R, ? superT> accumulator, BiConsumer<R ,R> combiner) 将元素收集到类型 R 的结果中。 - 在每个部分上,都会调用 supplier 来提供初始结果,调用 accumulator 来交替地将元素添加到结果中,并调用 combiner 来整合两个结果。
 
基本类型流
基本类型流:
- IntStream、LongStream 和 DoubleStream,用来直接存储基本类型值,而无需使用包装器;
 - 存储 short、char、byte 和 boolean,可以使用 IntStream;
 - 对于 float,可以使用 DoubleStream。
 
基本类型流的使用:
- 创建 IntStream,可以使用“IntStream.of”和“Arrays.stream”;
IntStream stream = IntStream.of(l, 1, 2, 3, 5); stream = Arrays.stream(values, from, to); // values is an int[] array
 - 与对象流一样,我们还可以使用静态的“generate”和“iterate”方法;(无限流)
 - IntStream 和 LongStream 有静态方法“range”和“rangeClosed”,可以生成步长为1 的整数范闱:
IntStream zeroToNinetyNine = IntStream.range(O, 100); // Upper bound is excluded IntStream zeroToHundred = IntStream.rangeClosed(0, 100); // Upper bound is included
 - CharSequence 接口拥有“codePoints”和“chars”方法,可以生成由字符的 Unicode 码或由 UTF-16 编码机制的码元构成的 IntStream:
String sentence = "\uD83S\u0D46 is the set of octonions."; // \uD835\uDD46 is the UTF-16 encoding of the letter @, unicode U+1D546 IntStream codes = sentence.codePoints(); // The stream with hex values 1D546 20 69 73 20 . . .
 
基本流类型的转换:
- 将“对象流”转换为“基本类型流”:可以用“mapToInt”、“mapToLong”和“mapToDouble”方法;
Stream<String> words = . . . ; IntStream lengths = words.mapToInt(String::length);
 - 将“基本类型流”转换为“对象流”:需要用“boxed”方法;
Stream<Integer> integers = IntStream.range(O, 100).boxed();
 
“基本类型流”与“对象流”的方法类似,但有差异:
- toArray 方法会返回基本类型数组。
 - 产生可选结果的方法会返回一个 OptionalInt 、OptionalLong 或 OptionalDouble。
- 这些类与 Optional 类类似,但是具有 getAsInt 、getAsLong 和 getAsDouble 方法,而不是 get 方法。
 
 - 具有返回总和、平均值、最大值和最小值的 sum 、average 、max 和 min 方法。对象流没有定义这些方法。
 - summaryStatisties 方法会产生一个类型为 IntSummaryStatistics 、LongSummaryStatistics 或 DoubleSummaryStatistics 的对象,它们可以同时报告流的总和、平均值、最大值和最小值。
 
- 相关方法:
 
Java.utit.stream.IntStream 8 static IntStream range(int startInclusive, int endExclusive) 产生一个由给定范围内的整数构成的 IntStream。 static IntStream rangeClosed(int startInclusive, int endInclusive) static IntStream of(int ... values) 产生一个由给定元素构成的 IntStream 。 int[] toArray() 产生一个由当前流中的元素构成的数组。 int sum() 产生当前流中元素的总和、平均值、最大值和最小值,或者从中可以获得这些结果的所有四种值的对象。 OptionalDouble average() OptionalInt max() OptionalInt min() IntSummaryStatistics summaryStatistics() Stream<Integer> boxed() 产生用于当前流中的元素的包装器对象流。 Java.util.stream.LongStream 8 static LongStream range(long startInclusive, long endExclusive) 用给定范围内的整数产生一个 LongStream。 static LongStream rangeClosed(long startInclusive , long endlnclusive) static LongStream of(long .. . values) 用给定元素产生一个 LongStream 。 1ong[] toArray() 用当前流中的元素产生一个数组。 1ong sum() 产生当前流中元素的总和、平均值、最大值和最小值,或者从中可以获得这些结果的所有四种值的对象。 OptionalDouble average() OptionalLong max() OptionalLong min() LongSummaryStatisties summaryStatisties() Stream<Long> boxed() 产生用千当前流中的元索的包装器对象流。 java.util.stream.DoubleStream 8 static DoubleStream of(double ... values) 用给定元素产生一个 DoubleStream。 double[] toArray() 用当前流中的元素产生一个数组。 double sum() 产生当前流中元素的总和、平均值、最大值和最小值,或者从中可以获得这些结果的所有四种值的对象。 OptionalDouble average() OptionalDouble max() OptionalDouble min() DoubleSummaryStatistics summaryStatistics() Stream<Double> boxed() 产生用于当前流中的元素的包装器对象流。 java.lang.CharSequence 1.0 IntStream codePoints() 8 产生由当前字符串的所有 Unicode 码点构成的流。 java.util.Random 1.0 IntStream ints () 产生随机数流。 - 如果提供了streamSize, 这个流就是具有给定数址元素的有限流。
 - 当提供了边界时,其元素将位于 randomNumberOrigin (包含)和 randomNumberBound(不包含)的区间内。
 
IntStream ints(int randomNumberOrigin, int randomNumberBound) 8 IntStream ints(long streamSize) 8 IntStream ints(long streamSize, int randomNumberOrigin, int randomNumberBound) 8 LongStream longs() 8 LongStream longs(long randomNumberOrigin, long randomNumberBound) 8 LongStream longs(long streamSize) 8 LongStream 1ongs(long streamSize, long randomNumberOrigin, long randomNumberBound) 8 DoubleStream doubles() 8 DoubleStream doubles(double randomNumberOrigin, double randomNumberBound) 8 DoubleStream doubles(long streamSize) 8 DoubleStream doubles(long streamSize, double randomNumberOrigin, double randomNumberBound) 8 java.util.Optional(Int | Long | Double) 8 static Optional(Int | Long | Double) of((int | long | double) value) 用所提供的基本类型值产生一个可选对象。 (int | long | double) getAs(Int | Long | Double)() 产生当前可选对象的值,或者在其为空时抛出一个 NoSuchElementException 异常。 (int | long | double) orElse((Int | long | double) other) 产生当前可选对象的值,或者在这个对象为空时产生可替代的值。 (int | long | double) orElseGet((Int | Long | Double)Supplier other) void ifPresent((Int | Long | Double)Consumer consumer) 如果当前可选对象不为空,则将其值传递给 consumer。 java.util.(Int | Long | Double)SummaryStatistics 8 long getCount() 产生收集到的元素的个数、总和、平均值、最大值和最小值。 (int | long | double) getSum() double getAverage() (int | long | double) getMax() (int | long | double) getMin() 
并行流
并行流的获取:
- 从任何集合中获取一个并行流:“Collection.parallelStream()”方法:
Stream<String> parallelWords = words.parallelStream();
 - 将任意的顺序流转换为并行流:“parallel”方法:
Stream<String> parallelWords = Stream.of(wordArray).parallel();
 
并行流的注意事项:
- 只要在终结方法执行时,流处于并行模式,那么所有的中间流操作都将被并行化。
 - 当流操作并行运行时,这些操作必须可以以任意顺序执行,才能使其返回结果与顺序执行时返回的结果相同。
- 示例:对字符串流中所有短单词计数:
 // 错误的方式: // 传递给 forEach 的函数会在多个并发线程中运行 // 由于每个都会更新共享的数组,所以多次运行这个程序时,很可能出现每次运行都会产生不同的计数值,而且每个都是错的。 int[] shortWords = new int[12]; words.para11e1Stream().forEach( s -> { if (s.length() < 12) shortWords[s.length()]++; }); // Error-race condition! System.out.print1n(Arrays.toString(shortWords));
- 如上:这是一种非常非常糟糕的代码。。如果
 // 推荐的方式: // 以字符串长度将其分为群组,然后分别计数 Map<lnteger, long> shortWordCounts = words.parallelStream() .filter(s -> s.length() < 10) .co11ect(groupingBy( String::length, counting()));
 - 传递给并行流操作的函数不应该被堵塞:并行流使用 fork-join 池来操作流的各个部分。如果多个流操作被阻塞,那么池可能就无法做任何事情了。
- 参见:Fork-Join框架
 
 
默认情况下,从有序集合(数组和列表)、范围、生成器和迭代产生的流,或者通过调用 Stream.sorted 产生的流,都是有序的。 它们的结果是按照原来元素的顺序累积的,因此是完全可预知的。如果运行相同的操作两次,将会得到完全相同的结果。
- 排序并不排斥高效的并行处理。
- 例如,当计算 stream.map(fun)时,流可以被划分为 n 的部分,它们会被并行地处理。然后,结果将会按照顺序重新组装起来。
 
 - 当放弃排序需求时(在流上调用“unordered”方法),有些操作可以被更有效地并行化。
- 在有序的流中,“distinct”会保留所有相同元素中的第一个,这对并行化是一种阻碍,因为处理每个部分的线程在其之前的所有部分都被处理完之前,并不知道应该丢弃哪些元素。如果可以接受保留唯一元素中任意一个的做法,那么所有部分就可以并行地处理(使用共享的集来跟踪重复元素)。
 
 - 还可以通过放弃排序要求来提高 limit 方法的速度。
- 例如:如果只想从流中取出任意 n 个元素,而并不在意到底要获取哪些,那么可以调用:
 Stream<String> sample = words.parallelStream().unordered().limit(n);
 - 由于合并映射表的代价很高昂,“Collectors.groupByConcurrent”方法使用了共享的并发映射表。为了从并行化中获益,映射表中值的顺序不会与流中的顺序相同。
Map<Integer, List<String>> result = words.paral1e1Stream().collect( Col1ectors.groupingByConcurrent(String::1ength)); // Values aren't collected in stream order
- 当然,如果使用独立于排序的下游收集器,那么就不必在意了:
 Map<Integer, Long> wordCounts = words.parallelStream() .collect( groupingByConcurrent( String::length, counting())) ;
 
不要修改在执行某项流操作后会将元素返回到流中的集合(即使这种修改是线程安全的)。 1、流并不会收集它们的数据,数据总是在单独的集合中。 2、如果修改了这样的集合,那么流操作的结果就是未定义的。 3、JDK 文档对这项需求并未做出任何约束,并且对顺序流和并行流都采用了这种处理方式。
更准确地讲,因为中间的流操作都是惰性的,所以直到执行终结操作时才对集合进行修改仍旧是可行的,但仍然不推荐这种修改。
- 例如,下面的操作尽管并不推荐,但是仍旧可以工作:
 // 不推荐的代码 List<String> wordList = ...; Stream<String> words = wordlist.stream(); wordlist.add("END"); long n = words.distinct().count();
- 但是,下面的代码是错误的:
 // 错误的代码 Stream<String> words = wordlist.stream(); words.forEach(s -> if(s.length() < 12) wordlist.remove(s)); // Error-interference,不能再流中修改集合
不要将所有的流都转换为并行流。只有在对已经位于内存中的数据执行大量计算操作时,才应该使用并行流。
为了让并行流正常工作,需要满足大批的条件:
- 数据应该在内存中。必须等到数据到达是非常低效的。
 - 流应该可以被高效地分成若干个子部分。
- 由数组或平衡二叉树支撑的流都可以工作得很好,但是 Stream.iterate 返回的结果不行。
 
 - 流操作的工作量应该具有较大的规模。
- 如果总工作负载并不是很大,那么搭建并行计算时所付出的代价就没有什么意义。
 
 - 流操作不应该被阻塞。
 
- 相关方法:
 
java.util.stream.BaseStream<T, S extends BaseStream<T,S>> 8 S parallel() 产生一个与当前流中元素相同的并行流。 S unordered() 产生一个与当前流中元素相同的无序流。 iava.util.Collection<E> 1.2 Stream<E> parallelStream() 8 用当前集合中的元素产生一个并行流。