“核心技术Ⅱ:流”的版本间差异

来自Wikioe
跳到导航 跳到搜索
第49行: 第49行:
#: 例如,如果我们只想查找前5 个长单词而不是所有长单词,那么filter 方法就会在匹配到第5 个单词后停止过滤。因此,我们甚至可以操作无限流。
#: 例如,如果我们只想查找前5 个长单词而不是所有长单词,那么filter 方法就会在匹配到第5 个单词后停止过滤。因此,我们甚至可以操作无限流。


=== 相关方法 ===
'''相关方法:'''
java.util.stream.Stream<T> 8
{| class="wikitable" style="width:100%;"
* Stream<T> filter(Predicate<? super T> p)
|-
*: 产生一个流,其中包含当前流中满足P 的所有元索。
! colspan="2"  style="text-align:left;"| java.util.stream.Stream<T> 8
* 1ong count()
|-
*: 产生当前流中元素的数批。这是一个终止操作。
| style="width:50%;" | Stream<T> filter(Predicate<? super T> p)
java.util.Collection<E> 1.2
| style="width:50%;" | 产生一个流,其中包含当前流中满足P 的所有元索。
* default Stream<E> stream()
|-
* default Stream<E> parallel Stream()
| 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()
|}


== 流的创建 ==
== 流的创建 ==

2021年10月31日 (日) 00:45的版本


关于 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();
    

以上:

  1. stream 和parallel Stream 方法会产生一个用于words 列表的stream。
  2. filter 方法会返回另一个流,其中只包含长度大于12 的单词。
  3. count 方法会将这个流化简为一个结果。

流与集合

流表面上活起来和集合很类似,都可以让我们转换和获取数据。但是,它们之间存在着显著的差异:

  1. 流并不存储其元素。
    这些元素可能存储在底层的栠合中,或者是按需生成的。
  2. 流的操作不会修改其数据源。
    例如,filter 方法不会从新的流中移除元素,而是会生成一个新的流,其中不包含被过滤掉的元素。
  3. 流的操作是尽可能惰性执行的。这意味若直至需要其结果时,操作才会执行。
    例如,如果我们只想查找前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()

流的创建

  1. 集合转换为流:
    用“Collection.stream()”方法将任何集合转换为一个流。
  2. 数组转换为流:
    1. 使用“Array.stream(array, from, to)”可以从数组中位于from (包括)和to (不包括)的元索中创建一个流。
    2. 使用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");
        
  3. 空流:
    使用静态的Stream.empty 方法,创建不包含任何元素的流;
    Stream<String> silence = Stream.empty();   // Generic type <String> is inferred; same as St ream. <St ri ng>empty()
    
  4. 无限流:
    1. generate 方法:接受一个不包含任何引元的函数(或者从技术上讲,是一个Supplier<T> 接口的对象)。
      Stream<String> echos = Stream.generate(() -> "Echo");   // 获得一个常批值的流:
      Stream<Double> randoms = Stream.generate(Math:: random);   // 获得一个随机数的流:
      
    2. iterate 方法:接受一个“种子”值,以及一个函数(从技术上讲,是一个UnaryOperation<T>), 并且会反复地将该函数应用到之前的结果上。
      Strea价<BigInteger> integers = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));
      // 第一个元素是种子Biglnteger.ZERO, 第二个元素是f(seed), 即l (作为大整数),下一个元素是f(f(seed)), 即2, 后续以此类推。
      


Java API中的其他流方法:

  1. Pattern 类有一个“splitAsStream”方法,它会按照某个正则表达式来分割一个“CharSequence”对象:
    Stream<String> words = Pattern,compile("\\PL+").splitAsStream(contents);
    
  2. 静态的“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<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 方法

  1. filter 转换会产生一个流,它的元素与某种条件相匹配。
    filter 的引元是Predicate<T>, 即从T 到boolean 的函数。
    List<String> wordlist = . . . ;
    Stream<String> longwords = wordlist.stream().filter(w -> w. length() > 12);
    
  2. map 方法用于按照某种方式来转换流中的值。
    // 1、使用函数式接口:
    Stream<String> lowercaseWords = words.stream().map(String::tolowerCase);
    // 2、或者使用lambda替换:
    Stream<String> firstletters = words.stream().map(s -> s.substring(O, 1));
    
  3. flatMap 方法用于“摊平由流构成的流”。
    (即,将包含流的流,变为流)
    // 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",...],”;
    

相关方法

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 应用于当前流中所有元素所产生的结果连接到一起而获得的。(注意,这里的每个结果都是一个流。)

抽取子流和连接流

  1. stream.limit(n):会返回一个新的流,它在n个元素之后结束(如果原来的流更短,那么就会在流结束时结束)。
    (对于裁剪无限流的尺寸特别有用。)
    Stream<Doub1e> randoms = Stream.generate(Math::random).1imit(lOO);   // 会产生一个包含100 个随机数的流
    
  2. stream.skip(n):会丢弃前n个元素。
    (在将文本分隔为单词时会显得很方便。)
    Stream<String> words = Stream.of(contents.split("\\PL+")).skip(l);   // 会跳过split方法产生字符串的第一个字符(空字符串)
    
  3. 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 的元素。

其他的流转换

  1. distinct:方法会返回一个流,它的元素是从原有流中,按照同样的顺序剔除重复元素后产生的。
    Stream<String> uniqueWords = Stream.of("merrily", "merrily", "merrily", "gently").distinct();  // Only one "merrily" is retained
    
  2. sorted:方法会返回一个流,它的元素是原有流中按照顺序排列的元素。
    (有多种sorted方法的变体可用:一种用于操作“Comparable”元素的流,另一种可以接受一个“Comparator”)
    Stream<String> longestFirst = words.stream().sorted(Comparator.comparing(String::1ength).reversed());
    
  3. 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()
  • Stream<T> sorted(Comparator<? super T> comparator)
    产生一个流,它的元素是当前流中的所有元素按照顺序排列的。第一个方法要求元素是实现了 Comparable 的类的实例。
  • Stream<T> peek(Consumer<? super T> action)
    产生一个流,它与当前流中的元素相同,在获取其中每个元素时,会将其传递给action。

流的终止操作

流的终止操作:即从流数据中获得答案。

简单约简

约简是一种终结操作(tennjnal operation), 它们会将流约简为可以在程序中使用的非流值。

  • 这些方法返回的是一个类型“Optional<T>”的值,它要么在其中包装了答案,要么表示没有任何值(因为流碰巧为空)。


常用的简单约简:

  1. count”方法,会返回流中元素的数量;
  2. max”、“min”方法,会返回流中元素的最大值和最小值;
    Optional<String> largest = words.max(String::compareToignoreCase);
    System.out.println("largest: " + largest.orElse(""));
    
  3. findFirst”方法,返回的是非空集合中的第一个值;
    (通常会在与filter 组合使用时显得很有用)
    Optional<String> startsWithQ = words.filter(s -> s.startsWith("Q")).findFirst();   // 找到第一个以字母Q 开头的单词
    
  4. findAny”方法,返回的是非空集合中的任意匹配值;
    Optional<String> startsWithQ = words.parallel().filter(s -> s.startsWith("Q")).findAny();
    
  5. anyMatch”方法,只返回是否存在匹配;
    (这个方法会接受一个断言引元Predicate,因此不需要使用filter)
    boolean aWordStartsWithQ = words.parallel().anyMatch(s -> s.startsWith("Q"));
    
  6. allMatch”方法,在所有元素匹配断言的情况下返回true;
  7. noneMatch”方法,在没有任何元素匹配断言的情况下返回true;

Optional 类型

Optional<T> 对象是一种包装器对象,要么包装了类型T 的对象,要么没有包装任何对象。

  • Optional<T> 类型被当作一种更安全的方式,用来替代类型T 的引用,这种引用要么引用某个对象,要么为null。

如何使用Optional值

使用Optional 的关键:

  1. 在值不存在的情况下会产生一个可替代物;
    // 在没有任何匹配时:使用某种默认值,可能是空字符串:
    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
    
  2. 在值存在的情况下才会使用这个值。
    • 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> c onsumer)
    如果该Optional 不为空,那么就将它的值传递给consumer 。
  • <U> Optiona1<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();
相关方法
  • T get()
    产生这个Optional 的值,或者在该Optional 为空时, 抛出一个NoSuchElementException对象。
  • boolean isPresent()
    如果该Optional 不为空,则返回true 。

创建Optional 值

有多个方法可以创建Optional 对象:

  1. “Optional.of(result)”
  2. “Optional.empty()”
  3. “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)
  • static <T> Optional<T> ofNullable(T value)
    产生一个具有给定值的Optional 。如果value 为nul1, 那么第一个方法会抛出一个NullPointerException 对象, 而第二个方法会产生一个空Optional 。
  • static <T> Optional<T> empty()
    产生一个空Optional 。

用flatMap 来构建Optional值的函数

  • (类比于“stream.flatMap()”)

“ptional.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<U>> mapper)
    产生将mapper 应用千当前的Optional 值所产生的结果,或者在当前Optional 为空时,返回一个空Optional 。

收集结果

当处理完流之后,通常会想要查看、收集其元素。

查看元素

  1. 调用“iterator”方法,它会产生可以用来访问元素的旧式风格的迭代器。
  2. 调用“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 。这是一种终结操作。

收集元素到集合

  1. stream.toArray()”:收集到数组中:
    • (如果想要让数组具有正确的类型,可以将其传递到数组构造器中)
    // 1、“stream.toArray()”:会返回一个Object[]数组;
    String[] result = stream.toArray()
    
    // 2、使用构造器,使数组有正确的类型
    String[] result = stream.toArray(String[]::new);   // stream.toArray() has type Object[]
    
  2. stream.collect()”:收集到其他目标中;
    • 该方法接受—个“Collector”接口的实例(Collectors类提供了大扯用于生成公共收集器的工厂方法);
    1. 收集到列表或集:
      List<String> resu1t = stream.co11ect(Co11ectors, toList());
      
      Set<String> result = stream.collect(Collectors.toSet());
      
    2. 控制获得的集的种类:
      TreeSet<String> result = stream.collect(Co11ectors.toCo11ection(TreeSet::new)) ;
      
    3. 通过连接操作来收集流中的所有字符串:
      String result= stream.collect(Collectors.joining());
      
    4. 在元素之间增加分隔符,可以将分隔符传递给joining 方法:
      String result= stream.collect(Collectors.joining(", "));
      
    5. 如果流中包含除字符串以外的其他对象,那么我们需要现将其转换为字符串:
      String result = stream.map(Object::toString).collect(Collectors.joining(", "));
      
    6. 如果想要将流的结果约简为总和、平均值、最大值或最小值,可以使用“summarizing(IntlLonglDouble)”方法中的某一个。
      • (这些方法会接受一个将流对象映射为数据的函数,同时,这些方法会产生类型为“(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> 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”方法有两个函数引元,它们用来产生映射表的键和值。

Map<Integer, String> idToName = people.collect(Co11ectors.toMap(Person::getId, Person::getName));
  1. 通常情况下,值应该是实际的元索,因此第二个函数可以使用“Function.identity()”;
    (“Function.identity()”:返回一个输出跟输入一样的Lambda表达式对象,等价于形如“t -> t”形式的Lambda表达式)
    Map<Integer, String> idToPerson = peop1e.collect(Collectors.toMap(Person::getId, Function.identity()));
    
  2. 如果有多个元素具有相同的键,那么就会存在冲突,收集器将会抛出一个“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));
    
  3. 如果想要得到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)
  • 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> Co11ector<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>> Coll ector<T, ?, M> toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier)
    产生一个收集器,它会产生一个映射表或并发映射表。keyMapper 和valueMapper 函数会应用于每个收集到的元素上,从而在所产生的映射表中生成一个键/值项。默认情况下,当两个元素产生相同的键时,会抛出一个IllegalStateException 异常。你可以提供一个mergeFunction 来合并具有相同键的值。默认情况下,其结果是一个HashMap 或ConcurrentHashMap 。你可以提供一个mapSupplier, 它会产生所期望的映射表实例。

群组和分区

  1. groupingBy”方法,将具有相同特性的值群聚成组;(群组)
    Map<String, List<Locale>> countryTolocales = 1oca1es.collect(Co11ectors.groupingBy(loca1e::getCountry));
    
  2. 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)
  • 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 方法会产生一个映射表,它的每个值都是一个列表。如果想要以某种方式来处理这些列表,就需要提供一个“下游收集器”。
例如,如果想耍获得集而不是列表,那么可以使用上一节中看到的“Collector.toSet”收集器:

Map<String, Set<Locale>> countrylolocaleSet = locales.collect(
   groupingBy(Locale::getCountry, toSet()));


Java 提供了多种可以将群组元素约简为数字的收集器:(方法均为“java.util.stream.Collectors.xxx”)

  1. “counting”会产生收集到的元素的个数:
    Map<String, Long> countryToLocaleCounts = locales.co11ect(
       groupingBy(Locale::get(ountry, counting()));
    // 可以对每个国家有多少个Locale 进行计数。
    
  2. “summing(Int|Long|Double)”会接受一个函数作为引元,将该函数应用到下游元素中,并产生它们的和:
    Map<String, Integer> stateToCityPopulation = cities.collect(
       groupingBy(City::getState, summingInt(City::getPopulation)));
    // 可以计算城市流中每个州的人口总和。
    
  3. “maxBy”和“minBy”会接受一个比较器,并产生下游元素中的最大值和最小值。例如:
    Map<String, Optional<City>> statelolargestCity = cities.co11ect(
       groupingBy(City::getState, maxBy(Comparator.comparing(City::getPopulation))));
    // 可以产生每个州中撮大的城市。
    
  4. “mapping”方法会产生将函数应用到下游结果上的收集器, 并将函数值传递给另一个收集器:
    Map<String, Optional<String>> stateTolongestCityName = cities.collect(
       groupingBy(City::getState,
          mapping(City::getName,
             maxBy(Comparator.comparing(String::1ength)))));
    
  • 将收集器组合起来是一种很强大的方式,但是它也可能会导致产生非常复杂的表达式。
    它们的最佳用法是与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 收集器收集到的具有相同键的元素。

其他

约简操作

  1. “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)”
    
  2. 通常,会有一个幺元值“e”:使得“e op x = x”,可以使用这个元素作为计算的起点:
    • 如果流为空,则会返回么元值,就再也不需要处理Optional 类了。
    List<lnteger> values= ... ;
    Integer sum = values.stream().reduce(O, (x, y) -> x + y);   // Computes O + vo + v1 + v2 + . . .
    
  3. 对于引元和结果的类型相同的函数:需要提供一种“累积器”函数(如:“(total, word) -> total + word.length()”),这个函数会被反复调用,产生累积的总和。
    • 但是,当计算被并行化时,会有多个这种类型的计算,需要将它们的结果合并。(因此需要提供第二个函数“组合器”来执行此处理)
    int result = words.reduce(O,
       (total, word) -> tota1 + word.length(),
       (total1, total2) -> totall + total2);
    
  4. 实践中,可能并不会频繁地用到reduce 方法。通常,映射为数字流并使用其方法来计算总和、最大值和最小值会更容易:
    • 因为它不涉及装箱操作,所以更简单也更高效;
    words.mapToInt(String::length).sum(),
    
  5. 有时reduce 会显得并不够通用,而应使用“collect”方法
    • 它会接受单个引元:
      1. 一个提供者,它会创建目标类型的新实例,例如散列集的构造器。
      2. 一个累积器,它会将一个元素添加到一个实例上,例如add 方法。
      3. 一个组合器,它会将两个实例合并成一个,例如addAll。
    BitSet result = stream.co11ect(BitSet::new, BitSet::set, BitSet::or);
    
相关方法
  • Optional<T> reduce(BinaryOperator<T> accumulator)
  • T reduce(T identity, BinaryOperator<T> accumulator)
  • <U> U reduce(U identity, BiFunction<U, ? super T,U> accumulator, BinaryOperator<U> combiner)
    用给定的accumulator 函数产生流中元素的累积总和。如果提供了么元,那么第一个被累计的元素就是该么元。如果提供了组合器,那么它可以用来将分别累积的各个部分整合成总和。
  • <R> Rcollect(Supplier<R> supplier, BiConsumer<R, ? superT> accumulator, BiConsumer<R ,R> combiner)
    将元素收集到类型R 的结果中。在每个部分上,都会调用supplier 来提供初始结果,调用accumulator 来交替地将元素添加到结果中,并调用combiner 来整合两个结果。

基本类型流

基本类型流:

  1. IntStream 、LongStream 和DoubleStream,用来直接存储基本类型值,而无需使用包装器;
  2. 存储short 、char 、byte 和boolean,可以使用IntStream;
  3. 对于float,可以使用DoubleStream。


基本类型流的使用:

  1. 创建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
    
  2. 与对象流一样,我们还可以使用静态的“generate”和“iterate”方法;
  3. 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
    
  4. 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 . . .
    


基本流类型的转换:

  1. 将“对象流”转换为“基本类型流”:可以用“mapToInt”、“mapToLong”和“mapToDouble”方法;
    Stream<String> words = . . . ;
    lntStream lengths = words.mapToInt(String::length);
    
  2. 将“基本类型流”转换为“对象流”:需要用“boxed”方法;
    Stream<lnteger> integers = IntStream.range(O, 100).boxed();
    


“基本类型流”与“对象流”的方法类似,但有差异:

  1. toArray 方法会返回基本类型数组。
  2. 产生可选结果的方法会返回一个OptionalInt 、OptionalLong 或OptionalDouble。
    这些类与Optional 类类似,但是具有getAslnt 、getAslong 和getAsDouble 方法,而不是get 方法。
  3. 具有返回总和、平均值、最大值和最小值的sum 、average 、max 和min 方法。对象流没有定义这些方法。
  4. 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()
    产生收集到的元素的个数、总和、平均值、最大值和最小值。

并行流

并行流的获取:

  1. 用“Collection.parallelStream()”方法从任何集合中获取一个并行流:
    Stream<String> parallelWords = words.parallelStream();
    
  2. “parallel”方法可以将任意的顺序流转换为并行流:
    Stream<String> parallelWords = Stream.of(wordArray).parallel();
    
  • 只要在终结方法执行时,流处于并行模式,那么所有的中间流操作都将被并行化。
  • 当流操作并行运行时,这些操作必须可以以任意顺序执行,才能使其返回结果与顺序执行时返回的结果相同。


并行流的使用:Fork-Join框架

传递给并行流操作的函数不应该被堵寒:
   并行流使用fork-join 池来操作流的各个部分。如果多个流操作被阻塞,那么池可能就无法做任何事情了。
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()));
  • 默认情况下,从有序集合(数组和列表)、范围、生成器和迭代产生的流,或者通过调用Stream.sorted 产生的流,都是有序的。
    它们的结果是按照原来元素的顺序累积的,因此是完全可预知的。如果运行相同的操作两次,将会得到完全相同的结果。
  • 当放弃排序需求时(在流上调用“unordered”方法),有些操作可以被更有效地并行化。
  • 在有序的流中,“distinct”会保留所有相同元素中的第一个,这对并行化是一种阻碍,因为处理每个部分的线程在其之前的所有部分都被处理完之前,并不知道应该丢弃哪些元素。


合并映射表的代价很高昂。正是因为这个原因,“Collectors.groupByConcurrent”方法使用了共享的并发映射表。为了从并行化中获益,映射表中值的顺序不会与流中的顺序相同。
Map<lnteger, List<Stri ng>> 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())) ;


关于并行流的修改:
# 流并不会收集它们的数据,数据总是在单独的集合中。
# 不要修改在执行某项流操作后会将元素返回到流中的集合(即使这种修改是线程安全的)。
# 因为中间的流操作都是惰性的,所以直到执行终结操作时才对集合进行修改仍旧是可行的。
// 不推荐的代码
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,不能再流中修改集合


不要将所有的流都转换为并行流。只有在对已经位于内存中的数据执行大量计算操作时,才应该使用并行流。

为了让并行流正常工作,需要满足大批的条件:

  1. 数据应该在内存中。
    必须等到数据到达是非常低效的。
  2. 流应该可以被高效地分成若干个子部分。
    由数组或平衡二叉树支撑的流都可以工作得很好,但是Stream.iterate返回的结果不行。
  3. 流操作的工作盐应该具有较大的规模。
    如果总工作负载并不是很大,那么搭建并行计算时所付出的代价就没有什么意义。
  4. 流操作不应该被阻塞。