查看“核心技术Ⅱ:流”的源代码
←
核心技术Ⅱ:流
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
[[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 个单词后停止过滤。因此,我们甚至可以操作无限流。 === 相关方法 === java.util.stream.Stream<T> 8 * Stream<T> filter(Predicate<? super T> p) *: 产生一个流,其中包含当前流中满足P 的所有元索。 * 1ong count() *: 产生当前流中元素的数批。这是一个终止操作。 java.util.Collection<E> 1.2 * default Stream<E> stream() * default Stream<E> parallel Stream() *: 产生当前集合中所有元素的顺序流或并行流。 == 流的创建 == # 集合转换为流: #: 用“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"> Strea价<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> === 相关方法 === 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<Stri ng> spl i tAsStream(CharSequence input) 8 *: 产生一个流,它的元素是输入中由该模式界定的部分。 java.nio.file.Files 7 * static Stream<String> lines(Path path) 8 * static Stream<Stri ng> lines(Path path, Charset cs) 8 *: 产生一个流, 它的元素是指定文件中的行,该文件的字符集为UTF-8 , 或者为指定的字符集。 java.util.function.Supplier<T> 8 * T get() *: 提供一个值。 == 流的转换 == '''流的转换会产生一个新的流,它的元素派生自另一个流中的元素。''' === 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、 Stream<Stream<String>> result = words.stream() .map(w -> letters(w)); // 2、 Stream<String> flatResult = words.stream().flatMap(w -> letters(w)); letters: public static Stream<String> letters(String s) { List<Stri ng> result = new Array List<>(); 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> ==== 相关方法 ==== 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个元素之后结束(如果原来的流更短,那么就会在流结束时结束)。 #:(对于裁剪无限流的尺寸特别有用。) #: <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> ==== 相关方法 ==== 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''':方法会返回一个流,它的元素是从原有流中,按照同样的顺序剔除重复元素后产生的。 #: <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> ==== 相关方法 ==== java.util.stream.Stream 8 * Stream<T> distinct() *: 产生一个流,包含当前流中所有不同的元素。 * Stream<T> sorted() * Stream<T> sorted(Comparator<? super T> comparator) *: 产生一个流,它的元素是当前流中的所有元素按照顺序排列的。第一个方法要求元素是实现了 Comparable 的类的实例。 * Stream<T> peek(Consumer<? super T> action) *: 产生一个流,它与当前流中的元素相同,在获取其中每个元素时,会将其传递给action。 == 流的终止操作 == 流的终止操作:即从流数据中获得答案。 === 简单约简 === 约简是一种终结操作(tennjnal operation), 它们会将流约简为可以在程序中使用的非流值。<br/> * 这些方法返回的是一个类型“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; === Optional 类型 === 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 resu 1t = opti ona 1 String. orEl se Throw(Illega1StateException::new); // Supply a method that yi e 1 ds 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> ===== 相关方法 ===== 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> c onsumer) *: 如果该Optional 不为空,那么就将它的值传递给consumer 。 * <nowiki><U> Optiona1<U> map(Function<? super T , ? extends U> mapper)</nowiki> *: 产生将该Optional 的值传递给mapper 后的结果,只要这个Optional 不为空且结果不为null , 否则产生一个空Optional 。 ==== 不适合使用Optional值的方式 ==== 【???】 <pre> 如果没有正确地使用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(); </pre> ===== 相关方法 ===== * 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()”。 #: <syntaxhighlight lang="java"> public static Optional<Double> inverse(Double x) { return x = 0 ? Optional.empty() : Optional.of(l / x); } </syntaxhighlight> ===== 相关方法 ===== java.util.Optional 8 * static <T> Optional<T> of(T value) * static <T> Optional<T> ofNullable(T value) *: 产生一个具有给定值的Optional 。如果value 为nul1, 那么第一个方法会抛出一个NullPointerException 对象, 而第二个方法会产生一个空Optional 。 * static <T> Optional<T> empty() *: 产生一个空Optional 。 ==== 用flatMap 来构建Optional值的函数 ==== *(类比于“stream.flatMap()”) “ptional.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> ===== 相关方法 ===== java.util.Optional 8 * <nowiki><U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper)</nowiki> *: 产生将mapper 应用千当前的Optional 值所产生的结果,或者在当前Optional 为空时,返回一个空Optional 。 === 收集结果 === 当处理完流之后,通常会想要查看、收集其元素。 ==== 查看元素 ==== # 调用“'''iterator'''”方法,它会产生可以用来访问元素的旧式风格的迭代器。 # 调用“'''forEach'''”方法,将某个函数应用于每个元素: #* 在并行流上,forEach会以任意顺序遍历各个元索; #* 如果想要按照流中的顺序来处理它们,可以调用“'''forEachOrdered'''”方法(会丧失并行处理的部分甚至全部优势); #: <syntaxhighlight lang="java"> stream.forEach(System.out::println); </syntaxhighlight> ===== 相关方法 ===== java.util.stream.BaseStream 8 * Iterator<T> iterator( ) *: 产生一个用于获取当前流中各个元素的迭代器。这是一种终结操作。 java.util.stream.Stream 8 * void forEach(Consumer<? super T> action) *: 在流的每个元素上砌用action 。这是一种终结操作。 ==== 收集元素到集合 ==== # “'''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(IntlLonglDouble)”方法中的某一个。 ##*(这些方法会接受一个将流对象映射为数据的函数,同时,这些方法会产生类型为“(Int|Long|Double)SummaryStatistics”的结果,同时计算总和、数最、平均值、最小值和最大值。) ##: <syntaxhighlight lang="java"> IntSummaryStatistics summary = stream.co11ect(Co11ectors.summarizingInt(String::length)); double averageWordLength = summary.getAverage(); double maxWordLength = summary.getMax(); </syntaxhighlight> ===== 相关方法 ===== java.util.stream.BaseStream 8 * Object[] toArray() * <A> A[] toArray(IntFunction<A[]> generator) *: 产生一个对象数组,或者在将引用A[]::new 传递给构造器时,返回一个A 类型的数组。这些操作都是终结操作。 * <R ,A> R collect(Collector<? super T,A,R> collector) *: 使用给定的收集器来收集当前流中的元素。Collectors 类有用于多种收集器的T厂方法。 java.util.stream.Collectors 8 * static <T> Collector<T,?, Li st<T»tol i st() * 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 Coll ector<CharSequence,?, String> joining(CharSequence delimiter) * static Coll ector<CharSequence,?, String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) *: 产生一个连接字符串的收集器。分隔符会翌于字符串之间,而第一个字符串之前可以有前缀,最后一个字符串之后可以有后缀。如果没有指定,那么它们都为空。 * static <T> Coll ector<T,?, IntSumnaryStati sties> sumnari zi ngInt(TolntFunction<? super T> mapper) * stat i c<T> Coll ector<T,?, LongSummaryStat i st i cs> summarizingLong(TolongFunction<? superT> mapper) * static <T> Collector<T,?,DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper) *: 产生能够生成(IntlLonglDouble)SummaryStatistics 对象的收集器,通过它可以获得将mapper 应用千每个元素后所产生的结果的个数、总和、平均值、最大值和最小值。 lntSummaryStatistics 8 LongSummaryStatistics 8 DoubleSummaryStatistics 8 * long getCount () *: 产生汇总后的元素的个数。 * (intllongldouble) getSum() * double getAverage() *: 产生汇总后的元素的总和或平均值,或者在没有任何元素时返回0 。 * (int 11 ong I double) getMax () * (intllongldouble) getMin() *: 产生汇总后的元素的最大值和最小值,或者在没有任何元素时,产生(Integer|Long|Double).(MAXI MIN)_VALUE。 ==== 收集到映射表中 ==== “'''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”方法。 ** 单个并发映射表可以用于并行集合处理。 ** 当使用并行流时,共享的映射表比合并映射表要更高效。 * 注意,元素不再是按照流中的顺序收集的,但是通常这不会有什么问题。 ===== 相关方法 ===== java.util.stream.Collector 8 * <nowiki>static<T, K, U> Collector<T, ?, Map<K, U> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)</nowiki> * <nowiki>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)</nowiki> * <nowiki>static <T, K, U, M extends Map<K, U> Co11ector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)</nowiki> * <nowiki>static <T, K, U> Collector<T, ?, ConcurrentMap<K, U>> toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper)</nowiki> * <nowiki>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)</nowiki> * <nowiki>static <T, K, U, M extends ConcurrentMap<K, U>> Coll ector<T, ?, M> toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)</nowiki> *: 产生一个收集器,它会产生一个映射表或并发映射表。keyMapper 和valueMapper 函数会应用于每个收集到的元素上,从而在所产生的映射表中生成一个键/值项。默认情况下,当两个元素产生相同的键时,会抛出一个IllegalStateException 异常。你可以提供一个mergeFunction 来合并具有相同键的值。默认情况下,其结果是一个HashMap 或ConcurrentHashMap 。你可以提供一个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 方法完全类似。 ===== 相关方法 ===== java.util.stream.Collector 8 * static<T,K> Collector<T, ?, Map<K,List<T>>> groupingBy(Function<? super T, ? extends K> classifier) * static<T,K> Collector<T, ?, ConcurrentMap<K, List<T>>> groupingByConcurrent(Function<? super T, ? extends K> classifier) *: 产生一个收集器,它会产生一个映射表或并发映射表,其键是将classifier 应用于所有收集到的元素上所产生的结果, 而值是由具有相同键的元素构成的一个个列表。 * static<T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) *: 产生一个收集器,它会产生一个映射表,其键是true/false , 而值是由满足/不满足断言的元素构成的列表。 ==== 下游收集器 ==== 【???】 groupingBy 方法会产生一个映射表,它的每个值都是一个列表。如果想要以某种方式来处理这些列表,就需要提供一个“下游收集器”。<br/> 例如,如果想耍获得集而不是列表,那么可以使用上一节中看到的“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::get(ountry, 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>> statelolargestCity = 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 、c ount 、max 或min 这样的方法。 ===== 相关方法 ===== java.util.stream.Collectors 8 * static <T> Co11ector<T, ?, Long> counting () *: 产生一个可以对收集到的元素进行计数的收集器。 * static <T> Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper) * static <T> Collector<T, ?, Long> summingLong(ToLongFunction<? super T> mapper) * static <T> Col1ector<T, ?, Double> summingDouble(ToDoubleFunction<? super T> mapper) *: 产生一个收集器,对将mapper 应用到收集到的元素上之后产生的值计算总和。 * static <T> Collector<T, ?, Optiona1<T>> maxBy(Comparator<? super T> comparator) * static <T> Collector<T, ? ,Optional<T>> minBy(Comparator<? super T> comparator) *: 产生一个收集器,使用comparator 指定的排序方法,计算收集到的元素中的最大值和最小值。 * static <T, U, A, R> Collector<T , ? , R> mapping(Function<? s upe r T, ? extends U> mapper, Collector<? super U, A, R> downstream) *: 产生一个收集器,它会产生一个映射表,其键是将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> ===== 相关方法 ===== * Optional<T> reduce(BinaryOperator<T> accumulator) * T reduce(T identity, BinaryOperator<T> accumulator) * <nowiki><U> U reduce(U identity, BiFunction<U, ? super T,U> accumulator, BinaryOperator<U> combiner)</nowiki> *: 用给定的accumulator 函数产生流中元素的累积总和。如果提供了么元,那么第一个被累计的元素就是该么元。如果提供了组合器,那么它可以用来将分别累积的各个部分整合成总和。 * <R> Rcollect(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”; #: <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 = . . . ; lntStream lengths = words.mapToInt(String::length); </syntaxhighlight> # 将“基本类型流”转换为“对象流”:需要用“boxed”方法; #: <syntaxhighlight lang="java"> Stream<lnteger> integers = IntStream.range(O, 100).boxed(); </syntaxhighlight> “基本类型流”与“对象流”的方法类似,但有差异: # toArray 方法会返回基本类型数组。 # 产生可选结果的方法会返回一个OptionalInt 、OptionalLong 或OptionalDouble。 #: 这些类与Optional 类类似,但是具有getAslnt 、getAslong 和getAsDouble 方法,而不是get 方法。 # 具有返回总和、平均值、最大值和最小值的sum 、average 、max 和min 方法。对象流没有定义这些方法。 # '''summaryStatisties''' 方法会产生一个类型为IntSummaryStatistics 、LongSummaryStatistics 或DoubleSummaryStatistics 的对象,它们可以同时报告流的总和、平均值、最大值和最小值。 ===== 相关方法 ===== Java.utit.stream.lntStream 8 * static IntStream range(int startlnclusive, int endExclusive) * static IntStream rangeClosed(int startinclusive, int endinclusive ) *: 产生一个由给定范围内的整数构成的IntStream。 * 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 ) * static LongStream rangeClosed(long startInclusive , long endlnclusive) *: 用给定范围内的整数产生一个LongStream 。 * 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 i nts () * IntStream ints(int randomNumberOrigin, . int randomNumberBound) 8 * IntStream ints(long streamSize) 8 * IntStream ints(long streamSize, int randomNumberOrigin, int randomNumberBound) 8 * Long Stream longs () 8 * Long Stream longs(long randomNumberOri gin, long randomNumberBound) 8 * LongStream longs(long streamSi ze) 8 * LongStream 1 ongs(long streamSi ze, long randomNumberOrigin, 1ong randomNumberBound) 8 * Doubl eStream doubles() 8 * DoubleStream doubles(double randomNumberOrigin, double randomNumberBound) 8 * DoubleStream doubles(long streamSize) 8 * DoubleStream doubles(long streamSize, double randomNumberOrigin, double randomNumberBound) 8 *: 产生随机数流。如果提供了streamSize, 这个流就是具有给定数址元素的有限流。当提供了边界时,其元素将位于randomNumberOri gin (包含)和randomNumberBound(不包含)的区间内。 java.util.Optional(lnt|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()”方法从任何集合中获取一个并行流: #: <syntaxhighlight lang="java"> Stream<String> parallelWords = words.parallelStream(); </syntaxhighlight> # “parallel”方法可以将任意的顺序流转换为并行流: #: <syntaxhighlight lang="java"> Stream<String> parallelWords = Stream.of(wordArray).parallel(); </syntaxhighlight> * 只要在终结方法执行时,流处于并行模式,那么所有的中间流操作都将被并行化。 * 当流操作并行运行时,这些操作必须可以以任意顺序执行,才能使其返回结果与顺序执行时返回的结果相同。 并行流的使用:【[[Fork-Join框架]]】 <pre> 传递给并行流操作的函数不应该被堵寒: 并行流使用fork-joi n 池来操作流的各个部分。如果多个流操作被阻塞,那么池可能就无法做任何事情了。 </pre> <syntaxhighlight lang="java"> 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)); // 这是一种非常非常糟糕的代码。传递给forEach 的函数会在多个并发线程中运行,每个都会更新共享的数组。 // 如果多次运行这个程序,很可能出现每次运行都会产生不同的计数值,而且每个都是错的。 // 可以使用以下方法: Map<lnteger, long> shortWordCounts = words.parallelStream() .filter(s -> s.length() < 10) .co11ect(groupingBy( String::length, counting())); </syntaxhighlight> * 默认情况下,从有序集合(数组和列表)、范围、生成器和迭代产生的流,或者通过调用Stream.sorted 产生的流,都是有序的。 *: 它们的结果是按照原来元素的顺序累积的,因此是完全可预知的。如果运行相同的操作两次,将会得到完全相同的结果。 * 当放弃排序需求时(在流上调用“'''unordered'''”方法),有些操作可以被更有效地并行化。 * 在有序的流中,“'''distinct'''”会保留所有相同元素中的第一个,这对并行化是一种阻碍,因为处理每个部分的线程在其之前的所有部分都被处理完之前,并不知道应该丢弃哪些元素。 <pre> 合并映射表的代价很高昂。正是因为这个原因,“Collectors.groupByConcurrent”方法使用了共享的并发映射表。为了从并行化中获益,映射表中值的顺序不会与流中的顺序相同。 </pre> <syntaxhighlight lang="java"> Map<lnteger, List<Stri ng>> 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> <pre> 关于并行流的修改: # 流并不会收集它们的数据,数据总是在单独的集合中。 # 不要修改在执行某项流操作后会将元素返回到流中的集合(即使这种修改是线程安全的)。 # 因为中间的流操作都是惰性的,所以直到执行终结操作时才对集合进行修改仍旧是可行的。 </pre> <syntaxhighlight lang="java"> // 不推荐的代码 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,不能再流中修改集合 </syntaxhighlight> <pre> 不要将所有的流都转换为并行流。只有在对已经位于内存中的数据执行大量计算操作时,才应该使用并行流。 </pre> 为了让并行流正常工作,需要满足大批的条件: # 数据应该在内存中。 #: 必须等到数据到达是非常低效的。 # 流应该可以被高效地分成若干个子部分。 #: 由数组或平衡二叉树支撑的流都可以工作得很好,但是Stream.iterate返回的结果不行。 # 流操作的工作盐应该具有较大的规模。 #: 如果总工作负载并不是很大,那么搭建并行计算时所付出的代价就没有什么意义。 # 流操作不应该被阻塞。
返回至“
核心技术Ⅱ:流
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
大陆简体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
笔记
服务器
数据库
后端
前端
工具
《To do list》
日常
阅读
电影
摄影
其他
Software
Windows
WIKIOE
所有分类
所有页面
侧边栏
站点日志
工具
链入页面
相关更改
特殊页面
页面信息