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

来自Wikioe
跳到导航 跳到搜索
第233行: 第233行:


=== 简单约简 ===
=== 简单约简 ===
----
约简是一种终结操作(tennjnal operation), 它们会将流约简为可以在程序中使用的非流值。<br/>
约简是一种终结操作(tennjnal operation), 它们会将流约简为可以在程序中使用的非流值。<br/>
* 这些方法返回的是一个类型“Optional<T>”的值,它要么在其中包装了答案,要么表示没有任何值(因为流碰巧为空)。
* 这些方法返回的是一个类型“Optional<T>”的值,它要么在其中包装了答案,要么表示没有任何值(因为流碰巧为空)。
第263行: 第262行:


=== Optional 类型 ===
=== Optional 类型 ===
----
Optional<T> 对象是一种包装器对象,要么包装了类型T 的对象,要么没有包装任何对象。
Optional<T> 对象是一种包装器对象,要么包装了类型T 的对象,要么没有包装任何对象。
* Optional<T> 类型被当作一种更安全的方式,用来替代类型T 的引用,这种引用要么引用某个对象,要么为null。
* Optional<T> 类型被当作一种更安全的方式,用来替代类型T 的引用,这种引用要么引用某个对象,要么为null。

2020年11月23日 (一) 00:00的版本


关于 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 的所有元索。
  • 1ong count()
    产生当前流中元素的数批。这是一个终止操作。

java.util.Collection<E> 1.2

  • default Stream<E> stream()
  • default Stream<E> parallel Stream()
    产生当前集合中所有元素的顺序流或并行流。

流的创建

  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值的方式

创建Optional 值

用flatMap 来构建Optional值的函数

收集结果

收集到映射表中

群组和分区

其他

下游收集器

约简操作

基本类型流

并行流