查看“核心技术Ⅱ:流”的源代码
←
核心技术Ⅱ:流
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
[[category:JavaCore]] == 关于 Java SE 8 的流库 == <pre> Stream它并不是一个容器,它只是对容器的功能进行了增强,添加了很多便利的操作,例如查找、过滤、分组、排序等一系列的操作。 并且有串行、并行两种执行模式,并行模式充分的利用了多核处理器的优势,使用fork/join框架进行了任务拆分,同时提高了执行速度。 简而言之,Stream就是提供了一种高效且易于使用的处理数据的方式。 </pre> 流提供了一种在比集合更高的概念级别上指定计算的数据视图,以“做什么而非怎么做”的方式处理集合。 === 从迭代到流的操作 === 处理集合时,通常会迭代遍历它的元素,并在每个元素上执行某项操作: <syntaxhighlight lang="java"> 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++; } </syntaxhighlight> 使用流时,相同的操作看起来像下面这样: <syntaxhighlight lang="java"> 1ong count = words.stream().filter(w -> w.length() > 12).count(); </syntaxhighlight> * 仅将stream 修改为parallelStream 就可以让流库以并行方式来执行过滤和计数: *: <syntaxhighlight lang="java"> 1ong count = words.parallelStream().filter(w -> w.length() > 12).count(); </syntaxhighlight> 以上: # stream 和parallel Stream 方法会产生一个用于words 列表的stream。 # filter 方法会返回另一个流,其中只包含长度大于12 的单词。 # count 方法会将这个流化简为一个结果。 === 流与集合 === 流表面上活起来和集合很类似,都可以让我们转换和获取数据。但是,它们之间存在着显著的差异: # 流并不存储其元素。 #: 这些元素可能存储在底层的栠合中,或者是按需生成的。 # 流的操作不会修改其数据源。 #: 例如,filter 方法不会从新的流中移除元素,而是会生成一个新的流,其中不包含被过滤掉的元素。 # 流的操作是尽可能惰性执行的。这意味若直至需要其结果时,操作才会执行。 #: 例如,如果我们只想查找前5 个长单词而不是所有长单词,那么filter 方法就会在匹配到第5 个单词后停止过滤。因此,我们甚至可以操作无限流。 :'''相关方法:''' :{| class="wikitable" style="width:100%;" |- ! colspan="2" style="text-align:left;"| java.util.stream.Stream<T> 8 |- | style="width:50%;" | Stream<T> filter(Predicate<? super T> p) | style="width:50%;" | 产生一个流,其中包含当前流中满足P 的所有元索。 |- | style="width:50%;" | long count() | style="width:50%;" | 产生当前流中元素的数批。这是一个终止操作。 |- ! colspan="2" style="text-align:left;"| java.util.Collection<E> 1.2 |- | style="width:50%;" | default Stream<E> stream() | style="width:50%;" rowspan="2" | 产生当前集合中所有元素的顺序流或并行流。 |- | style="width:50%;" | default Stream<E> parallelStream() |} == 流的创建 == # '''集合转换为流''': #: 用“'''Collection.stream()'''”方法将任何集合转换为一个流。 # '''数组转换为流''': ## 使用“'''Array.stream(array, from, to)'''”可以从数组中位于 from (包括)和 to (不包括)的元索中创建一个流。 ## 使用 Stream 静态的“'''of'''”方法,将数组转换为流: ##: <syntaxhighlight lang="java"> Stream<String> words= Stream.of(contents.split("\\PL+")); // split returns a String[] array </syntaxhighlight> ##* of 方法具有【可变长参数】,因此我们可以构建具有任意数值引元的流: ##*: <syntaxhighlight lang="java"> Stream<String> song = Stream.of("gently", "down", "the", "stream"); </syntaxhighlight> # '''空流''': #: 使用静态的 Stream.empty 方法,创建不包含任何元素的流; #: <syntaxhighlight lang="java"> Stream<String> silence = Stream.empty(); // Generic type <String> is inferred; same as St ream. <St ri ng>empty() </syntaxhighlight> # '''无限流''': ## '''generate''' 方法:接受一个不包含任何引元的函数(或者从技术上讲,是一个 Supplier<T> 接口的对象)。 ##: <syntaxhighlight lang="java"> Stream<String> echos = Stream.generate(() -> "Echo"); // 获得一个常批值的流: Stream<Double> randoms = Stream.generate(Math:: random); // 获得一个随机数的流: </syntaxhighlight> ## '''iterate''' 方法:接受一个“种子”值,以及一个函数(从技术上讲,是一个UnaryOperation<T>), 并且会反复地将该函数应用到之前的结果上。 ##: <syntaxhighlight lang="java"> Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE)); // 第一个元素是种子Biglnteger.ZERO, 第二个元素是f(seed), 即l (作为大整数),下一个元素是f(f(seed)), 即2, 后续以此类推。 </syntaxhighlight> Java API中的其他流方法: # Pattern 类有一个“'''splitAsStream'''”方法,它会按照某个正则表达式来分割一个“CharSequence”对象: #: <syntaxhighlight lang="java"> Stream<String> words = Pattern.compile("\\PL+").splitAsStream(contents); </syntaxhighlight> # 静态的“'''Files.lines()'''”方法会返回一个包含了文件中所有行的 Stream: #: <syntaxhighlight lang="java"> try (Stream<String> lines = Files.lines(path)) { ...Process lines... } </syntaxhighlight> :'''相关方法:''' :{| class="wikitable" style="width:100%;" |- ! colspan="2" style="text-align:left;"| java.util.stream.Stream 8 |- | style="width:50%;" | static <T> Stream<T> of(T .. . values) | style="width:50%;" | 产生一个元素为给定值的流。 |- | style="width:50%;" | static <T> Stream<T> empty() | style="width:50%;" | 产生一个不包含任何元素的流。 |- | style="width:50%;" | static <T> Stream<T> generate(Supplier<T> s) | style="width:50%;" | 产生一个无限流,它的值是通过反复调用函数s 而构建的。 |- | style="width:50%;" | static <T> Stream<T> iterate(T seed , UnaryOperator<T> f ) | style="width:50%;" | 产生一个无限流,它的元素包含种子、在种子上调用f 产生的值、在前一个元素上调用f 产生的值,等等。 |- ! colspan="2" style="text-align:left;"| java.util.Arrays 1.2 |- | style="width:50%;" | static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) 8 | style="width:50%;" | 产生一个流,它的元素是由数组中指定范围内的元素构成的。 |- ! colspan="2" style="text-align:left;"| java.util.regex.Pattem 1.4 |- | style="width:50%;" | Stream<String> splitAsStream(CharSequence input) 8 | style="width:50%;" | 产生一个流,它的元素是输入中由该模式界定的部分。 |- ! colspan="2" style="text-align:left;"| java.nio.file.Files 7 |- | style="width:50%;" | static Stream<String> lines(Path path) 8 | style="width:50%;" rowspan="2" | 产生一个流,它的元素是指定文件中的行,该文件的字符集为 UTF-8,或者为指定的字符集。 |- | style="width:50%;" | static Stream<String> lines(Path path, Charset cs) 8 |- ! colspan="2" style="text-align:left;"| java.util.function.Supplier<T> 8 |- | style="width:50%;" | T get() | style="width:50%;" | 提供一个值。 |} == 流的转换 == '''流的转换会产生一个新的流,它的元素派生自另一个流中的元素。''' === filter 、map 和 flatMap 方法 === # '''filter''' 转换会产生一个流,它的元素与某种条件相匹配。 #: filter 的引元是 '''Predicate<T>''', 即从 T 到 boolean 的函数。 #: <syntaxhighlight lang="java"> List<String> wordlist = . . . ; Stream<String> longwords = wordlist.stream().filter(w -> w.length() > 12); </syntaxhighlight> # '''map''' 方法用于按照某种方式来转换流中的值。 #: <syntaxhighlight lang="java"> // 1、使用函数式接口: Stream<String> lowercaseWords = words.stream().map(String::tolowerCase); // 2、或者使用lambda替换: Stream<String> firstletters = words.stream().map(s -> s.substring(O, 1)); </syntaxhighlight> # '''flatMap''' 方法用于“摊平由流构成的流”。 #: (即,'''将包含流的流,变为流''') #: <syntaxhighlight lang="java"> // 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",...],”; </syntaxhighlight> :'''相关方法:''' :{| class="wikitable" style="width:100%;" |- ! colspan="2" style="text-align:left;"| java.util.stream.Stream 8 |- | style="width:50%;" | Stream<T> filter(Predicate<? super T> predicate) | style="width:50%;" | 产生一个流,它包含当前流中所有满足断言条件的元索。 |- | style="width:50%;" | <R> Stream<R> map(Function<? super T, ? extends R> mapper) | style="width:50%;" | 产生一个流,它包含将 mapper 应用于当前流中所有元素所产生的结果。 |- | style="width:50%;" | <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) | style="width:50%;" | 产生一个流,它是通过将 mapper 应用于当前流中所有元素所产生的结果连接到一起而获得的。(注意,这里的每个结果都是一个流。) |} === 抽取子流和连接流 === # '''stream.limit(n)''':会返回一个新的流,它在n个元素之后结束(如果原来的流更短,那么就会在流结束时结束)。 #:(对于裁剪无限流的尺寸特别有用。) #: <syntaxhighlight lang="java"> Stream<Doub1e> randoms = Stream.generate(Math::random).1imit(lOO); // 会产生一个包含100 个随机数的流 </syntaxhighlight> # '''stream.skip(n)''':会丢弃前n个元素。 #:(在将文本分隔为单词时会显得很方便。) #: <syntaxhighlight lang="java"> Stream<String> words = Stream.of(contents.split("\\PL+")).skip(l); // 会跳过split方法产生字符串的第一个字符(空字符串) </syntaxhighlight> # '''Stream.concat(Stream<? extends T> a, Stream<? extends T> b)''':静态方法,用于拼接两个流。 #* 第一个流不应该是无限的,否则第二个流没有处理机会。 #: <syntaxhighlight lang="java"> Stream<String> combined= Stream.concat(1etters("Hello"), 1etters("Wor1d")); // Yields the stream ["H", "e", "l", "1", "o", "W", "o", "r", "l", "d") </syntaxhighlight> :'''相关方法:''' :{| class="wikitable" style="width:100%;" |- ! colspan="2" style="text-align:left;"| java:util.stream.Stream 8 |- | style="width:50%;" | Stream<T> limit(long maxSize) | style="width:50%;" | 产生一个流,其中包含了当前流中最初的 maxSize 个元素。 |- | style="width:50%;" | Stream<T> skip(long n) | style="width:50%;" | 产生一个流,它的元素是当前流中除了前 n 个元素之外的所有元素。 |- | style="width:50%;" | static<T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) | style="width:50%;" | 产生一个流,它的元素是 a 的元素后面跟着 b 的元素。 |} === 其他的流转换 === # '''distinct''':方法会返回一个流,它的元素是从原有流中,按照同样的顺序剔除重复元素后产生的。 #: <syntaxhighlight lang="java"> Stream<String> uniqueWords = Stream.of("merrily", "merrily", "merrily", "gently").distinct(); // Only one "merrily" is retained </syntaxhighlight> # '''sorted''':方法会返回一个流,它的元素是原有流中按照顺序排列的元素。 #:(有多种sorted方法的变体可用:一种用于操作“Comparable”元素的流,另一种可以接受一个“Comparator”) #: <syntaxhighlight lang="java"> Stream<String> longestFirst = words.stream().sorted(Comparator.comparing(String::1ength).reversed()); </syntaxhighlight> # '''peek''':元素与原来流中的元索相同,但是在每次获取一个元素时,都会调用一个函数。 #:(对于调试可以让peek调用一个设置断点的方法。) #: <syntaxhighlight lang="java"> Object[] powers= Stream.iterate(l.O, p -> p * 2).peek(e -> System.out.println("Fetching" + e)).1imit(20).toArray(); // 当实际访问一个元素时,就会打印出来一条消息。 </syntaxhighlight> :'''相关方法:''' :{| class="wikitable" style="width:100%;" |- ! colspan="2" style="text-align:left;"| java.util.stream.Stream 8 |- | style="width:50%;" | Stream<T> distinct() | style="width:50%;" | 产生一个流,包含当前流中所有不同的元素。 |- | style="width:50%;" | Stream<T> sorted() | style="width:50%;" rowspan="2" | 产生一个流,它的元素是当前流中的所有元素按照顺序排列的。 第一个方法要求元素是实现了 Comparable 的类的实例。 |- | style="width:50%;" | Stream<T> sorted(Comparator<? super T> comparator) |- | style="width:50%;" | Stream<T> peek(Consumer<? super T> action) | style="width:50%;" | 产生一个流,它与当前流中的元素相同,在获取其中每个元素时,会将其传递给action。 |} == 流的终止操作 == 流的终止操作:即从流数据中获得答案。 === 简单约简 === 约简是一种终结操作(tennjnal operation),它们会将流约简为可以在程序中使用的非流值。 * 这些方法返回的是一个类型“'''Optional<T>'''”的值,它要么在其中包装了答案,要么表示没有任何值(因为流碰巧为空)。 常用的简单约简: # “'''count'''”方法,会返回流中元素的数量; # “'''max'''”、“'''min'''”方法,会返回流中元素的最大值和最小值; #: <syntaxhighlight lang="java"> Optional<String> largest = words.max(String::compareToIgnoreCase); System.out.println("largest: " + largest.orElse("")); </syntaxhighlight> # “'''findFirst'''”方法,返回的是非空集合中的第一个值; #:(通常会在与filter 组合使用时显得很有用) #: <syntaxhighlight lang="java"> Optional<String> startsWithQ = words.filter(s -> s.startsWith("Q")).findFirst(); // 找到第一个以字母Q 开头的单词 </syntaxhighlight> # “'''findAny'''”方法,返回的是非空集合中的任意匹配值; #: <syntaxhighlight lang="java"> Optional<String> startsWithQ = words.parallel().filter(s -> s.startsWith("Q")).findAny(); </syntaxhighlight> # “'''anyMatch'''”方法,只返回是否存在匹配; #:(这个方法会接受一个断言引元Predicate,因此不需要使用filter) #: <syntaxhighlight lang="java"> boolean aWordStartsWithQ = words.parallel().anyMatch(s -> s.startsWith("Q")); </syntaxhighlight> # “'''allMatch'''”方法,在所有元素匹配断言的情况下返回 true; # “'''noneMatch'''”方法,在没有任何元素匹配断言的情况下返回 true; === <span style="color: green">Optional 类型</span> === ---- '''Optional<T> 对象是一种包装器对象,要么包装了类型 T 的对象,要么没有包装任何对象。''' * Optional<T> 类型被当作一种更安全的方式,用来替代类型 T 的引用,这种引用要么引用某个对象,要么为 null。 ==== 如何使用 Optional 值 ==== 使用 Optional 的关键: # 在值不存在的情况下会产生一个可替代物: #: <syntaxhighlight lang="java"> // 在没有任何匹配时:使用某种默认值,可能是空字符串: 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 </syntaxhighlight> # 在值存在的情况下才会使用这个值: #: “'''ifPresent'''”方法:接受一个函数。如果该可选值存在,那么它会被传递给该函数。否则不会发生任何事情。 #*(当调用 ifPresent 时,从该函数不会返回任何值。如果想要处理函数的结果,应该使用 '''map''') #: <syntaxhighlight lang="java"> // 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。 </syntaxhighlight> :'''相关方法:''' :{| class="wikitable" style="width:100%;" |- ! colspan="2" style="text-align:left;"| java.util.Optional 8 |- | style="width:50%;" | T '''orElse'''(T other) | style="width:50%;" | 产生这个 Optional 的值,或者在该 Optional 为空时,产生 other。 |- | style="width:50%;" | T '''orElseGet'''(Supplier<? extends T> other) | style="width:50%;" | 产生这个 Optional 的值,或者在该 Optional 为空时,产生调用 other 的结果。 |- | style="width:50%;" | <X extends Throwable> T '''orElseThrow'''(Supplier<? extends X> exceptionSupplier) | style="width:50%;" | 产生这个 Optional 的值,或者在该 Optional 为空时,抛出调用 exceptionSupplier 的结果。 |- | style="width:50%;" | void '''ifPresent'''(Consumer<? super T> consumer) | style="width:50%;" | 如果该 Optional 不为空,那么就将它的值传递给 consumer 。 |- | style="width:50%;" | <nowiki><U> Optional<U></nowiki> '''map'''(Function<? super T , ? extends U> mapper) | style="width:50%;" | 产生将该 Optional 的值传递给 mapper 后的结果,只要这个 Optional 不为空且结果不为 null , 否则产生一个空 Optional。 |} ==== 不适合使用 Optional 值的方式 ==== 如果没有正确地使用 Optional 值,那么相比较以往的得到“某物或null”的方式,你并没有得到任何好处。 # '''get''' 方法会在 Optional 值存在的情况下获得其中包装的元素,或者在不存在的情况下抛出一个 '''NoSuchElementException''' 对象。 #: 因此, #: <syntaxhighlight lang="Java" highlight=""> Optional<T> optionalValue = ...; optionalValue.get().someMethod(); </syntaxhighlight> #: 并不比下面的方式更安全: #: <syntaxhighlight lang="Java" highlight=""> T value = ...; va1ue.someMethod(); </syntaxhighlight> # '''isPresent''' 方法会报告某个 Optional<T> 对象是否具有一个值。 #: 但是, #: <syntaxhighlight lang="Java" highlight=""> if(optionalVa1ue.isPresent()) optionalValue.get().someMethod(); </syntaxhighlight> #: 并不比下面的方式更容易处理: #: <syntaxhighlight lang="Java" highlight=""> if (value != null) value.someMethod(); </syntaxhighlight> :'''相关方法:''' :{| class="wikitable" style="width:100%;" |- ! colspan="2" style="text-align:left;"| java.util.Optional 8 |- | style="width:50%;" | T '''get'''() | style="width:50%;" | 产生这个 Optional 的值,或者在该 Optional 为空时, 抛出一个 NoSuchElementException 对象。 |- | style="width:50%;" | boolean '''isPresent'''() | style="width:50%;" | 如果该 Optional 不为空,则返回 true。 |} ==== 创建 Optional 值 ==== 有多个方法可以创建 Optional 对象: # “Optional.'''of'''(result)” # “Optional.'''empty'''()” # “Optional.'''ofNullable'''(obj)”:在 obj 不为 null 的情况下返回“Optional.of(obj)”, 否则会返回“Optional.empty()”。 #: <syntaxhighlight lang="java"> public static Optional<Double> inverse(Double x) { return x = 0 ? Optional.empty() : Optional.of(l / x); } </syntaxhighlight> :'''相关方法:''' :{| class="wikitable" style="width:100%;" |- ! colspan="2" style="text-align:left;"| java.util.Optional 8 |- | style="width:50%;" | static <T> Optional<T> '''of'''(T value) | style="width:50%;" rowspan="2" | 产生一个具有给定值的 Optional。 * 如果 value 为 nul1, 那么第一个方法会抛出一个 NullPointerException 对象, 而第二个方法会产生一个空 Optional。 |- | style="width:50%;" | static <T> Optional<T> '''ofNullable'''(T value) |- | style="width:50%;" | static <T> Optional<T> '''empty'''() | style="width:50%;" | 产生一个空 Optional。 |} ==== 用 flatMap 来构建 Optional 值的函数 ==== *(类比于“stream.flatMap()”) “Optional.flatMap()”用来'''将流计算过程中的方法连接起来''': <pre> 假设你有一个可以产生 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 表达式,那么就可以重复此过程。 </pre> 可以直接将对 flatMap 的调用链接起来,从而构建由这些步骤构成的管道,只有所有步骤都成功时,该管道才会成功。 <syntaxhighlight lang="java"> 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); </syntaxhighlight> :'''相关方法:''' :{| class="wikitable" style="width:100%;" |- ! colspan="2" style="text-align:left;"| java.util.Optional 8 |- | style="width:50%;" | <nowiki><U> Optional<U></nowiki> '''flatMap'''(Function<? super T,Optional<U>> mapper) | style="width:50%;" | 产生将 mapper 应用于当前的 Optional 值所产生的结果,或者在当前 Optional 为空时,返回一个空 Optional。 |} === 收集结果 === ---- 当处理完流之后,通常会想要查看、收集其元素。 ==== 查看元素 ==== # 调用“'''iterator'''”方法,它会产生可以用来访问元素的旧式风格的迭代器。 # 调用“'''forEach'''”方法,将某个函数应用于每个元素: #* 在并行流上,forEach会以任意顺序遍历各个元索; #* 如果想要按照流中的顺序来处理它们,可以调用“'''forEachOrdered'''”方法(会丧失并行处理的部分甚至全部优势); #: <syntaxhighlight lang="java"> stream.forEach(System.out::println); </syntaxhighlight> :'''相关方法:''' :{| class="wikitable" style="width:100%;" |- ! colspan="2" style="text-align:left;"| java.util.stream.BaseStream 8 |- | style="width:50%;" | Iterator<T> iterator( ) | style="width:50%;" | 产生一个用于获取当前流中各个元素的迭代器。 * 这是一种终结操作。 |- ! colspan="2" style="text-align:left;"| java.util.stream.Stream 8 |- | style="width:50%;" | void forEach(Consumer<? super T> action) | style="width:50%;" | 在流的每个元素上砌用 action。 * 这是一种终结操作。 |} ==== 收集元素到集合(Collection) ==== # “'''stream.toArray()'''”:收集到数组中: #*(如果想要让数组具有正确的类型,可以将其传递到数组构造器中) #: <syntaxhighlight lang="java"> // 1、“stream.toArray()”:会返回一个Object[]数组; String[] result = stream.toArray(); // 2、使用构造器,使数组有正确的类型 String[] result = stream.toArray(String[]::new); // stream.toArray() has type Object[] </syntaxhighlight> # “'''stream.collect()'''”:收集到其他目标中; #* 该方法接受—个“Collector”接口的实例(Collectors类提供了大量用于生成公共收集器的工厂方法); ## 收集到列表或集: ##: <syntaxhighlight lang="java"> List<String> resu1t = stream.co11ect(Co11ectors.toList()); 或 Set<String> result = stream.collect(Collectors.toSet()); </syntaxhighlight> ## 控制获得的集的种类: ##: <syntaxhighlight lang="java"> TreeSet<String> result = stream.collect(Co11ectors.toCo11ection(TreeSet::new)) ; </syntaxhighlight> ## 通过连接操作来收集流中的所有字符串: ##: <syntaxhighlight lang="java"> String result= stream.collect(Collectors.joining()); </syntaxhighlight> ## 在元素之间增加分隔符,可以将分隔符传递给joining 方法: ##: <syntaxhighlight lang="java"> String result= stream.collect(Collectors.joining(", ")); </syntaxhighlight> ## 如果流中包含除字符串以外的其他对象,那么我们需要现将其转换为字符串: ##: <syntaxhighlight lang="java"> String result = stream.map(Object::toString).collect(Collectors.joining(", ")); </syntaxhighlight> ## 如果想要将流的结果约简为总和、平均值、最大值或最小值,可以使用“summarizing(Int | Long | Double)”方法中的某一个。 ##*(这些方法会接受一个将流对象映射为数据的函数,同时,这些方法会产生类型为“(Int | Long | Double)SummaryStatistics”的结果,同时计算总和、数最、平均值、最小值和最大值。) ##: <syntaxhighlight lang="java"> IntSummaryStatistics summary = stream.co11ect(Co11ectors.summarizingInt(String::length)); double averageWordLength = summary.getAverage(); double maxWordLength = summary.getMax(); </syntaxhighlight> :'''相关方法:''' :{| class="wikitable mw-collapsible mw-collapsed" style="width:100%;" |- ! colspan="2" style="text-align:left;"| java.util.stream.BaseStream 8 |- | style="width:50%;" | Object[] toArray() | style="width:50%;" rowspan="2" | 产生一个对象数组,或者在将引用A[]::new 传递给构造器时,返回一个A 类型的数组。 * 这些操作都是终结操作。 |- | style="width:50%;" | <A> A[] toArray(IntFunction<A[]> generator) |- | style="width:50%;" | <R, A> R collect(Collector<? super T,A,R> collector) | style="width:50%;" | 使用给定的收集器来收集当前流中的元素。 * Collectors 类有用于多种收集器的工厂方法。 |- ! colspan="2" style="text-align:left;"| java.util.stream.Collectors 8 |- | style="width:50%;" | static <T> Collector<T, ?, List<T>> toList() | style="width:50%;" rowspan="2" | 产生一个将元素收集到列表或集中的收集器。 |- | style="width:50%;" | static <T> Collector<T, ? , Set<T>> toSet() |- | style="width:50%;" | static <T, C extends Collection<T>> Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) | style="width:50%;" | 产生一个将元素收集到任意集合中的收集器。可以传递一个诸如TreeSet::new 的构造器引用。 |- | style="width:50%;" | static Collector<CharSequence, ?, String> joining() | 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, CharSequence prefix, CharSequence suffix) |- | 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%;" | static <T> Collector<T, ?, LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper) |- | style="width:50%;" | static <T> Collector<T, ?, DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper) |- ! colspan="2" style="text-align:left;"| IntSummaryStatistics 8 |- ! colspan="2" style="text-align:left;"| LongSummaryStatistics 8 |- ! colspan="2" style="text-align:left;"| DoubleSummaryStatistics 8 |- | style="width:50%;" | long getCount () | style="width:50%;" | 产生汇总后的元素的个数。 |- | style="width:50%;" | (int | long | double) getSum() | style="width:50%;" rowspan="2" | 产生汇总后的元素的总和或平均值,或者在没有任何元素时返回 0。 |- | style="width:50%;" | double getAverage() |- | style="width:50%;" | (int | long | double) getMax () | style="width:50%;" rowspan="2" | 产生汇总后的元素的最大值和最小值,或者在没有任何元素时,产生 (Integer | Long | Double).(MAX | MIN)_VALUE。 |- | style="width:50%;" | (int | long | double) getMin() |} ==== 收集到映射表(Map) ==== “'''Collectors.toMap'''”方法有两个函数引元,它们用来产生映射表的键和值。 <syntaxhighlight lang="java"> Map<Integer, String> idToName = people.collect(Co11ectors.toMap(Person::getId, Person::getName)); </syntaxhighlight> # 通常情况下,值应该是实际的元素,因此第二个函数可以使用“Function.identity()”; #:(“Function.identity()”:返回一个输出跟输入一样的 Lambda 表达式对象,等价于形如“t -> t”形式的 Lambda 表达式) #: <syntaxhighlight lang="java"> Map<Integer, String> idToPerson = peop1e.collect(Collectors.toMap(Person::getId, Function.identity())); </syntaxhighlight> # 如果有多个元素具有相同的键,那么就会存在冲突,收集器将会抛出一个“'''IllegalStateException'''”对象。 #: 可以通过提供第 3 个函数引元来覆盖这种行为: #: <syntaxhighlight lang="java"> Stream<Locale> loca1es = Stream.of(Locale.getAvailablelocales()); Map<String, String> languageNames = locales.collect( Co11ectors.toMap( Loca1e::getDisplaylanguage, l -> l.getDisplaylanguage(l), (existingValue, newValue) -> existingValue)); </syntaxhighlight> # 如果想要得到 TreeMap, 那么可以将构造器作为第 4 个引元来提供(合并函数): #: <syntaxhighlight lang="java"> Map<lnteger, Person> idToPerson = people.collect( Co11ectors.toMap( Person::getld , Function.identity(), (existingValue, newValue) -> { throw new IllegalStateException(); }, TreeMap::new)); </syntaxhighlight> 对于每一个 toMap 方法,都有一个等价的可以产生'''并发映射表'''的“'''toConcurrentMap'''”方法。【???】 * 单个并发映射表可以用于并行集合处理。 * 当使用并行流时,共享的映射表比合并映射表要更高效。 * 元素不再是按照流中的顺序收集的(但是通常这不会有什么问题)。 :'''相关方法:''' :{| class="wikitable mw-collapsible mw-collapsed" style="width:100%;" |- ! colspan="2" style="text-align:left;"| java.util.stream.Collector 8 |- | 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" | 产生一个收集器,它会产生一个映射表或并发映射表。 * keyMapper 和valueMapper 函数会应用于每个收集到的元素上,从而在所产生的映射表中生成一个键/值项。 * 默认情况下,当两个元素产生相同的键时,会抛出一个 IllegalStateException 异常。你可以提供一个mergeFunction 来合并具有相同键的值。 * 默认情况下,其结果是一个 HashMap 或 ConcurrentHashMap 。你可以提供一个 mapSupplier, 它会产生所期望的映射表实例。 |- | 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%;" | 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%;" | 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%;" | 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%;" | 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'''”方法,将具有相同特性的值群聚成组;('''群组''') #: <syntaxhighlight lang="java"> Map<String, List<Locale>> countryTolocales = 1oca1es.collect(Co11ectors.groupingBy(loca1e::getCountry)); </syntaxhighlight> # “'''partitioningBy'''”方法;('''分区''') #: 当分类函数是断言函数(即返回 boolean 值的函数)时,流的元素可以分区为两个列表:该函数返回 true 的元素和其他的元素。在这种情况下,使用 partitioningBy 比使用 groupingBy 要更高效。 #: <syntaxhighlight lang="java"> Map<Boolean, List<locale>> englishAndOtherlocales = locales.collect(Collectors.partitioningBy(l -> l.getlanguage().equals("en"))); List<locale> englishloca1es = englishAndOtherlocales.get(true); </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> Java 提供了多种可以将群组元素约简为数字的收集器:(方法均为“'''java.util.stream.Collectors.xxx'''”) # “counting”会产生收集到的元素的个数: #: <syntaxhighlight lang="java"> Map<String, Long> countryToLocaleCounts = locales.co11ect( groupingBy(Locale::getCountry, counting())); // 可以对每个国家有多少个 Locale 进行计数。 </syntaxhighlight> # “summing(Int | Long | Double)”会接受一个函数作为引元,将该函数应用到下游元素中,并产生它们的和: #: <syntaxhighlight lang="java"> Map<String, Integer> stateToCityPopulation = cities.collect( groupingBy(City::getState, summingInt(City::getPopulation))); // 可以计算城市流中每个州的人口总和。 </syntaxhighlight> # “maxBy”和“minBy”会接受一个比较器,并产生下游元素中的最大值和最小值。例如: #: <syntaxhighlight lang="java"> Map<String, Optional<City>> stateToLargestCity = cities.co11ect( groupingBy(City::getState, maxBy(Comparator.comparing(City::getPopulation)))); // 可以产生每个州中最大的城市。 </syntaxhighlight> # “mapping”方法会产生将函数应用到下游结果上的收集器, 并将函数值传递给另一个收集器: #: <syntaxhighlight lang="java"> Map<String, Optional<String>> stateToLongestCityName = cities.collect( groupingBy(City::getState, mapping(City::getName, maxBy(Comparator.comparing(String::1ength))))); </syntaxhighlight> * 将收集器组合起来是一种很强大的方式,但是它也可能会导致产生非常复杂的表达式。 *: 它们的最佳用法是与 groupingBy 和 partitioningBy 一起处理“下游的”映射表中的值。 *: 否则,应该直接在流上应用诸如 map、reduce、count、max 或 min 这样的方法。 :'''相关方法:''' :{| 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 方法有一项约简操作 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) 。 #: <syntaxhighlight lang="java"> List<lnteger> values = ... ; Optional<lnteger> sum = values.stream().reduce((x, y) -> x + y); // 或“reduce(Integer::sum)” </syntaxhighlight> # 通常,会有一个'''幺元值“e”''':使得“e op x = x”,可以使用这个元素作为计算的起点: #* 如果流为空,则会返回幺元值,就再也不需要处理 Optional 类了。 #: <syntaxhighlight lang="java"> List<lnteger> values= ... ; Integer sum = values.stream().reduce(O, (x, y) -> x + y); // Computes O + vo + v1 + v2 + . . . </syntaxhighlight> # 对于引元和结果的类型相同的函数:需要提供一种'''“累积器”函数'''(如:“(total, word) -> total + word.length()”),这个函数会被反复调用,产生累积的总和。 #* 但是,当计算被并行化时,会有多个这种类型的计算,需要将它们的结果合并。(因此需要提供第二个函数“'''组合器'''”来执行此处理) #: <syntaxhighlight lang="java"> int result = words.reduce(O, (total, word) -> tota1 + word.length(), (total1, total2) -> totall + total2); </syntaxhighlight> # 实践中,可能并不会频繁地用到 reduce 方法。通常,'''映射为数字流'''并使用其方法来计算总和、最大值和最小值会更容易: #* 因为它不涉及装箱操作,所以更简单也更高效; #: <syntaxhighlight lang="java"> words.mapToInt(String::length).sum(), </syntaxhighlight> # 有时 reduce 会显得并不够通用,而应使用'''“collect”方法''': #* 它会接受单个引元: #*# 一个'''提供者''',它会创建目标类型的新实例,例如散列集的构造器。 #*# 一个'''累积器''',它会将一个元素添加到一个实例上,例如 add 方法。 #*# 一个'''组合器''',它会将两个实例合并成一个,例如 addAll。 #: <syntaxhighlight lang="java"> BitSet result = stream.co11ect(BitSet::new, BitSet::set, BitSet::or); </syntaxhighlight> :'''相关方法:''' :{| class="wikitable" style="width:100%;" |- ! 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"> IntStream stream = IntStream.of(l, 1, 2, 3, 5); stream = Arrays.stream(values, from, to); // values is an int[] array </syntaxhighlight> # 与对象流一样,我们还可以使用静态的“generate”和“iterate”方法;(无限流) # IntStream 和 LongStream 有静态方法“range”和“rangeClosed”,可以生成步长为1 的整数范闱: #: <syntaxhighlight lang="java"> IntStream zeroToNinetyNine = IntStream.range(O, 100); // Upper bound is excluded IntStream zeroToHundred = IntStream.rangeClosed(0, 100); // Upper bound is included </syntaxhighlight> # CharSequence 接口拥有“codePoints”和“chars”方法,可以生成由字符的 Unicode 码或由 UTF-16 编码机制的码元构成的 IntStream: #: <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 IntStream codes = sentence.codePoints(); // The stream with hex values 1D546 20 69 73 20 . . . </syntaxhighlight> 基本流类型的转换: # 将'''“对象流”转换为“基本类型流”''':可以用“'''mapToInt'''”、“'''mapToLong'''”和“'''mapToDouble'''”方法; #: <syntaxhighlight lang="java"> Stream<String> words = . . . ; IntStream lengths = words.mapToInt(String::length); </syntaxhighlight> # 将'''“基本类型流”转换为“对象流”''':需要用“'''boxed'''”方法; #: <syntaxhighlight lang="java"> Stream<Integer> integers = IntStream.range(O, 100).boxed(); </syntaxhighlight> “基本类型流”与“对象流”的方法类似,但有差异: # toArray 方法会返回基本类型数组。 # 产生可选结果的方法会返回一个 '''OptionalInt''' 、'''OptionalLong''' 或 '''OptionalDouble'''。 #: 这些类与 Optional 类类似,但是具有 getAsInt 、getAsLong 和 getAsDouble 方法,而不是 get 方法。 # 具有返回总和、平均值、最大值和最小值的 sum 、average 、max 和 min 方法。对象流没有定义这些方法。 # '''summaryStatisties''' 方法会产生一个类型为 IntSummaryStatistics 、LongSummaryStatistics 或 DoubleSummaryStatistics 的对象,它们可以同时报告流的总和、平均值、最大值和最小值。 :'''相关方法:''' :{| 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() | 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) | 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" | 产生当前流中元素的总和、平均值、最大值和最小值,或者从中可以获得这些结果的所有四种值的对象。 |- | style="width:50%;" | OptionalDouble average() |- | style="width:50%;" | OptionalLong max() |- | style="width:50%;" | OptionalLong min() |- | 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() | 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() |- | 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"> Stream<String> parallelWords = words.parallelStream(); </syntaxhighlight> # '''将任意的顺序流转换为并行流''':“parallel”方法: #: <syntaxhighlight lang="java"> Stream<String> parallelWords = Stream.of(wordArray).parallel(); </syntaxhighlight> 并行流的注意事项: * '''只要在终结方法执行时,流处于并行模式,那么所有的中间流操作都将被并行化。''' * 当流操作并行运行时,'''这些操作必须可以以任意顺序执行''',才能使其返回结果与顺序执行时返回的结果相同。 *: 示例:对字符串流中所有短单词计数: *: <syntaxhighlight lang="java"> // 错误的方式: // 传递给 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)); </syntaxhighlight> *: 如上:这是一种非常非常糟糕的代码。。如果 *: <syntaxhighlight lang="java"> // 推荐的方式: // 以字符串长度将其分为群组,然后分别计数 Map<lnteger, long> shortWordCounts = words.parallelStream() .filter(s -> s.length() < 10) .co11ect(groupingBy( String::length, counting())); </syntaxhighlight> * '''传递给并行流操作的函数不应该被堵塞''':并行流使用 fork-join 池来操作流的各个部分。如果多个流操作被阻塞,那么池可能就无法做任何事情了。 ** 参见:<big><span style="color: green">'''[[Fork-Join框架]]'''</span></big> '''默认情况下,从有序集合(数组和列表)、范围、生成器和迭代产生的流,或者通过调用 Stream.sorted 产生的流,都是有序的。''' 它们的结果是按照原来元素的顺序累积的,因此是完全可预知的。如果运行相同的操作两次,将会得到完全相同的结果。 * 排序并不排斥高效的并行处理。 *: 例如,当计算 stream.map(fun)时,流可以被划分为 n 的部分,它们会被并行地处理。然后,结果将会按照顺序重新组装起来。 * 当放弃排序需求时(在流上调用“'''unordered'''”方法),有些操作可以被更有效地并行化。 *: 在有序的流中,“'''distinct'''”会保留所有相同元素中的第一个,这对并行化是一种阻碍,因为处理每个部分的线程在其之前的所有部分都被处理完之前,并不知道应该丢弃哪些元素。如果可以接受保留唯一元素中任意一个的做法,那么所有部分就可以并行地处理(使用共享的集来跟踪重复元素)。 * 还可以通过放弃排序要求来提高 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)); // Values aren't collected in stream order </syntaxhighlight> *: 当然,如果使用独立于排序的下游收集器,那么就不必在意了: *: <syntaxhighlight lang="java"> Map<Integer, Long> wordCounts = words.parallelStream() .collect( groupingByConcurrent( String::length, counting())) ; </syntaxhighlight> '''不要修改在执行某项流操作后会将元素返回到流中的集合(即使这种修改是线程安全的)。''' 1、流并不会收集它们的数据,数据总是在单独的集合中。 2、如果修改了这样的集合,那么流操作的结果就是未定义的。 3、JDK 文档对这项需求并未做出任何约束,并且对顺序流和并行流都采用了这种处理方式。 更准确地讲,因为中间的流操作都是惰性的,所以直到执行终结操作时才对集合进行修改仍旧是可行的,但仍然不推荐这种修改。 : 例如,下面的操作尽管并不推荐,但是仍旧可以工作: : <syntaxhighlight lang="java"> // 不推荐的代码 List<String> wordList = ...; Stream<String> words = wordlist.stream(); wordlist.add("END"); long n = words.distinct().count(); </syntaxhighlight> : 但是,下面的代码是错误的: : <syntaxhighlight lang="java"> // 错误的代码 Stream<String> words = wordlist.stream(); words.forEach(s -> if(s.length() < 12) wordlist.remove(s)); // Error-interference,不能再流中修改集合 </syntaxhighlight> '''不要将所有的流都转换为并行流。只有在对已经位于内存中的数据执行大量计算操作时,才应该使用并行流。''' 为了让并行流正常工作,需要满足大批的条件: # 数据应该在内存中。必须等到数据到达是非常低效的。 # 流应该可以被高效地分成若干个子部分。 #: 由数组或平衡二叉树支撑的流都可以工作得很好,但是 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%;" | 用当前集合中的元素产生一个并行流。 |}
返回至“
核心技术Ⅱ:流
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
大陆简体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
笔记
服务器
数据库
后端
前端
工具
《To do list》
日常
阅读
电影
摄影
其他
Software
Windows
WIKIOE
所有分类
所有页面
侧边栏
站点日志
工具
链入页面
相关更改
特殊页面
页面信息