• 初识Java 12-3 流


    目录

    终结操作

    将流转换为一个数组(toArray)

    在每个流元素上应用某个终结操作(forEach)

    收集操作(collect)

    组合所有的流元素(reduce)

    匹配(*Match)

    选择一个元素(find*)

    获得流相关的信息


    本笔记参考自: 《On Java 中文版》


    终结操作

    ||| 终结操作:这些操作会接受一个流,并生成一个最终结果。

            终结操作不会把任何东西发给某个后端的流。因此,终结操作总是我们在一个管线内可以做的最后一件事

    将流转换为一个数组(toArray)

            可以通过toArray()操作将流转换为数组:

    • toArray():将流元素转换到适当的数组中。
    • toArray(generator)generator用于在特定情况下分配自己的数组存储。

            在流操作生成的内容必须以数组形式进行使用时,toArray()就很有用了:

    【例子:将随机数存储在数组中,以流的形式复用】

    1. import java.util.Arrays;
    2. import java.util.Random;
    3. import java.util.stream.IntStream;
    4. public class RandInts {
    5. private static int[] rints =
    6. new Random(47)
    7. .ints(0, 1000)
    8. .limit(100)
    9. .toArray();
    10. public static IntStream rands() {
    11. return Arrays.stream(rints);
    12. }
    13. }

            通过这种方式,我们可以保证每次得到的是相同的流。


    在每个流元素上应用某个终结操作(forEach)

            有两个很常见的终结操作:

    1. forEach(Consumer)
    2. forEachOrdered(Consumer):这个版本可以确保forEach对元素的操作顺序是原始的流的顺序。

            通过使用parallel()操作,可以使forEach以任何顺序操作元素。

        parallel()的介绍:这一操作会让Java尝试在多个处理器上执行操作。parallel()可以将流进行分割,并在不同处理器上运行每个流。

            下面的例子通过引入parallel()来展示forEachOrdered(Consumer)的作用。

    【例子:forEachOrdered(Consumer)的作用】

    1. import static streams.RandInts.*;
    2. public class ForEach {
    3. static final int SZ = 14;
    4. public static void main(String[] args) {
    5. rands().limit(SZ)
    6. .forEach(n -> System.out.format("%d ", n));
    7. System.out.println();
    8. rands().limit(SZ)
    9. .parallel()
    10. .forEach(n -> System.out.format("%d ", n));
    11. System.out.println();
    12. rands().limit(SZ)
    13. .parallel()
    14. .forEachOrdered(n -> System.out.format("%d ", n));
    15. System.out.println();
    16. }
    17. }

            程序执行的结果是:

            在第一个流中,因为没有使用parallel(),所以结果显示的顺序就是它们从rands()中出现的顺序。而在第二个流中,parallel()的使用使得输出的顺序发生了变化。这就是因为有多个处理器在处理这个问题(若多执行几次,会发现输出的顺序会不一样,这就是多处理器处理带来的不确定性)

            在最后一个流中,尽管使用了parallel(),但forEachOrdered()使得结果回到了原始的顺序。

        所以,对非parallel()的流,使用forEachOrdered()不会有任何影响。


    收集操作(collect)

    • collect(Collector):使用这个Collector将流元素累加到一个结果集合中。
    • collect(Supplier, BiConsumer, BiConsumer):和上一个collect()不同的地方在于:
      • Supplier会创建一个新的结果集合。
      • 第一个BiConsumer用来将下一个元素包含到结果集合中。
      • 第二个BiConsumer用来将两个值进行组合。

        collect()的第二个版本,会在每次被调用时,使用Supplier生成一个新的结果集合。这些集合就是通过第二个BiConsumer组合成一个最终结果的。

            可以将流元素收集到任何特定种类的集合中。例如:假设我们需要把条目最终放入到一个TreeSet中。尽管Collectors中没有特定的toTreeSet()方法,但我们可以使用Collectors.toCollection(),将任何类型的构造器引用传递给toCollection()

    【例子:将提取的单词放入到TreeSet()中】

    1. import java.nio.file.Files;
    2. import java.nio.file.Paths;
    3. import java.util.Arrays;
    4. import java.util.Set;
    5. import java.util.TreeSet;
    6. import java.util.stream.Collectors;
    7. public class TreeSetOfWords {
    8. public static void main(String[] arg) throws Exception {
    9. Set words2 =
    10. Files.lines(Paths.get("TreeSetOfWords.java"))
    11. .flatMap(s -> Arrays.stream(s.split("\\W+")))
    12. .filter(s -> !s.matches("\\d+")) // 删除数字
    13. .map(String::trim) // 删除可能存在的留白
    14. .filter(s -> s.length() > 2)
    15. .limit(100)
    16. .collect(Collectors.toCollection(TreeSet::new));
    17. System.out.println(words2);
    18. }
    19. }

            程序执行的结果是:

            在这里,Arrays.stream(s.split("\\W+"))会将接收的文本行进行分割,获得的数组会变为Stream,然后其结果又通过flatMap()被展开成一个由单词组成的Stream

    【例子:从流生成一个Map

    1. import java.util.Iterator;
    2. import java.util.Map;
    3. import java.util.Random;
    4. import java.util.stream.Collectors;
    5. import java.util.stream.Stream;
    6. class Pair { // 一个基本的数据对象Pair
    7. public final Character c;
    8. public final Integer i;
    9. public Pair(Character c, Integer i) {
    10. this.c = c;
    11. this.i = i;
    12. }
    13. public Character getC() {
    14. return c;
    15. }
    16. public Integer getI() {
    17. return i;
    18. }
    19. @Override
    20. public String toString() {
    21. return "Pair{" +
    22. "c=" + c +
    23. ", i=" + i +
    24. '}';
    25. }
    26. }
    27. class RandomPair {
    28. Random rand = new Random(47);
    29. // 这是一个无限大的迭代器,指向随机生成的大写字母
    30. Iterator capChars = rand.ints(65, 91) // 65~91,即大写字母对应的ASCII值
    31. .mapToObj(i -> (char) i)
    32. .iterator();
    33. public Stream stream() { // 生成一个Pair流
    34. return rand.ints(100, 1000).distinct() // 移除流中的重复元素
    35. .mapToObj(i -> new Pair(capChars.next(), i)); // 组合成Pair对象的流
    36. }
    37. }
    38. public class MapCollector {
    39. public static void main(String[] args) {
    40. Map map =
    41. new RandomPair().stream()
    42. .limit(8)
    43. .collect(
    44. Collectors.toMap(Pair::getI, Pair::getC));
    45. System.out.println(map);
    46. }
    47. }

            程序执行的结果是:

            在大多数情况下,java.util.stream.Collectors中都会存在我们所需的Collector。而如果找不到我们所需要的,这时候就可以使用collect()的第二种形式。

    【例子:collect()的第二种形式】

    1. import java.util.ArrayList;
    2. public class SpecialCollector {
    3. public static void main(String[] arg) throws Exception {
    4. ArrayList words =
    5. FileToWords.stream("Cheese.dat")
    6. .collect(ArrayList::new, // 创建新的结果集合
    7. ArrayList::add, // 将下一个元素加入集合中
    8. ArrayList::addAll); // 组合所有的ArrayList
    9. words.stream()
    10. .filter(s -> s.equals("cheese"))
    11. .forEach(System.out::println);
    12. }
    13. }

            程序执行的结果是:


    组合所有的流元素(reduce)

    • reduce(BinaryOperator):使用BinaryOperator来组合所有的元素。若流为空,返回一个Optional。
    • reduce(identity, BinaryOperator):和上述一致,不同之处在于会将identity作为这个组合的初始值。因此若流为空,会得到一个identity作为结果。
    • reduce(identity, BiFuncton, BinaryOperator)BiFunction提供累加器,BinaryOperator提供组合函数。这种形式会更为高效。但若只论效果,可以通过组合显式的map()reduce()操作来替代。

        这些操作之所以使用reduce命名,是因为它们会获取一系列的输入元素,并将它们组合(简化)成一个单一的汇总结果。

    【例子:reduce()操作的演示】

    1. import java.util.Random;
    2. import java.util.stream.Stream;
    3. class Frobnitz {
    4. int size;
    5. Frobnitz(int sz) {
    6. size = sz;
    7. }
    8. @Override
    9. public String toString() {
    10. return "Frobnitz{" +
    11. "size=" + size +
    12. '}';
    13. }
    14. // 生成器
    15. static Random rand = new Random(47);
    16. static final int BOUND = 100;
    17. static Frobnitz supply() {
    18. return new Frobnitz(rand.nextInt(BOUND));
    19. }
    20. }
    21. public class Reduce {
    22. public static void main(String[] args) {
    23. Stream.generate(Frobnitz::supply)
    24. .limit(10)
    25. .peek(System.out::println)
    26. .reduce((fr0, fr1) -> fr0.size < 50 ? fr0 : fr1) // 若fr0的size小于50,接受fr0,否则接受fr1
    27. .ifPresent(System.out::println);
    28. }
    29. }

            程序执行的结果是:

            Forbnitz有一个自己的生成器,叫做supply()。在上述例子中,我们把方法引用Frobnitz::supply传递给了Stream.generate,因为它和Supplier是签名兼容的(又称结构一致性)。

        Supplier是一个函数式接口。在这里Supplier相当于一个返回Frobnitz对象的无参函数。

            在使用reduce()时,我们没有使用identity作为初始值。这意味着reduce()会生成一个Optional,当结果不为empty时,才会执行Consumer(即lambda表达式)。再看lambda表达式本身:

    (fr0, fr1) -> fr0.size < 50 ? fr0 : fr1

    其中的第一个参数fr0是上次调用这个reduce()时带回的结果,第二个参数fr1是来自流中的新值。


    匹配(*Match)

    • allMatch(Predicate):通过Predicate检测流中的元素,若每一个元素结果都为true,则返回true。若遇到false,则发生短路 —— 在遇到第一个false后,停止计算。
    • anyMatch(Predicate):同样使用Predicate进行检测,若任何一个元素得到true,则返回true。在遇到第一个true时,发生短路。
    • noneMatch(Predicate):进行检测,若没有元素得到true,则返回true。在遇到第一个true时,发生短路。

    【例子:Match引发的短路行为】

    1. import java.util.function.BiPredicate;
    2. import java.util.function.Predicate;
    3. import java.util.stream.IntStream;
    4. import java.util.stream.Stream;
    5. interface Matcher extends // 能够匹配所有的Stream::*Match函数的模式
    6. BiPredicate, Predicate> {
    7. }
    8. public class Matching {
    9. static void show(Matcher match, int val) {
    10. System.out.println(
    11. match.test(
    12. IntStream.rangeClosed(1, 9) // 返回一个从1~9递增的Integer序列
    13. .boxed()
    14. .peek(n -> System.out.format("%d ", n)),
    15. n -> n < val
    16. )
    17. );
    18. }
    19. public static void main(String[] args) {
    20. show(Stream::allMatch, 10);
    21. show(Stream::allMatch, 4);
    22. show(Stream::anyMatch, 2);
    23. show(Stream::anyMatch, 0);
    24. show(Stream::noneMatch, 5);
    25. show(Stream::noneMatch, 0);
    26. }
    27. }

            程序执行的结果是:

            BiPredicate是一个二元谓词,所以他会接受两个参数,并返回truefalse。其中,第一个参数是我们要测试的数值的流,第二个参数是谓词Predicate本身。


    选择一个元素(find*)

    • findFirst():返回一个Optional,其中包含了流中的第一个元素。若流中没有元素,则返回Optional.empty
    • findAny():返回一个Optional,其中包含了流中的某个元素。若流中没有元素,则返回Optional.empty

    【例子:find*操作的使用】

    1. import static streams.RandInts.*;
    2. public class SelectElement {
    3. public static void main(String[] args) {
    4. System.out.println(rands().findFirst().getAsInt());
    5. System.out.println(rands()
    6. .parallel().findFirst().getAsInt());
    7. System.out.println(rands().findAny().getAsInt());
    8. System.out.println(rands()
    9. .parallel().findAny().getAsInt());
    10. }
    11. }

            程序执行的结果是:

            无论流是否并行,findFirst()总会选择流中的第一个元素。但findAny()有些不同,在非并行的流中,findAny()会选择第一个元素,而当这个流是并行流时,findAny()有可能选择第一个元素之外的元素。

            若需要某个流中的最后一个元素,可以使用reduce()

    【例子:选择流中的最后一个元素】

    1. import java.util.Optional;
    2. import java.util.OptionalInt;
    3. import java.util.stream.IntStream;
    4. import java.util.stream.Stream;
    5. public class LastElement {
    6. public static void main(String[] args) {
    7. OptionalInt last = IntStream.range(10, 20)
    8. .reduce((n1, n2) -> n2);
    9. System.out.println(last.orElse(-1));
    10. // 对于非数值对象
    11. Optional lastobj =
    12. Stream.of("one", "two", "three")
    13. .reduce((n1, n2) -> n2);
    14. System.out.println(
    15. lastobj.orElse("不存在任何元素"));
    16. }
    17. }

            程序执行的结果是:

            reduce((n1, n2) -> n2)语句可以用两个元素这的后一个替换这两个元素,通过这种方式就可以获得流中的最后一个元素了。


    获得流相关的信息

    • count():获得流中元素的数量。
    • max(Comparator):通过Comparator确定这个流中的“最大元素”。
    • min(Comparator):确定这个流中的“最小元素”。

    【例子:使用String预设的Comparator获取信息】

    1. public class Informational {
    2. public static void main(String[] args) throws Exception {
    3. System.out.println(
    4. FileToWords.stream("Cheese.dat").count());
    5. System.out.println(
    6. FileToWords.stream("Cheese.dat")
    7. .min(String.CASE_INSENSITIVE_ORDER)
    8. .orElse("NONE"));
    9. System.out.println(
    10. FileToWords.stream("Cheese.dat")
    11. .max(String.CASE_INSENSITIVE_ORDER)
    12. .orElse("NONE"));
    13. }
    14. }

            程序执行的结果是:

            其中,max()min()都会返回Optional,这里出现的orElse()是用来获取其中的值的。

    获得数值化流相关的信息

    • average():获得平均值。
    • max()min():因为处理的是数值化的流,所以不需要Comparator
    • sum():将流中的数值进行累加。
    • summaryStatistics():返回可能有用的摘要数据(但我们也可以使用直接方法获取这些数据)。

    【例子:数值化流的信息获取(以IntStream为例)】

    1. import static streams.RandInts.*;
    2. public class NumericStreamInfo {
    3. public static void main(String[] args) {
    4. System.out.println(rands().average().getAsDouble());
    5. System.out.println(rands().max().getAsInt());
    6. System.out.println(rands().min().getAsInt());
    7. System.out.println(rands().sum());
    8. System.out.println(rands().summaryStatistics());
    9. }
    10. }

            程序执行的结果是:

  • 相关阅读:
    使用jconsole监控SpringbootJVM(JDK11)
    Android 13 新特性及适配指南
    算法通关村第十五关:白银挑战-海量数据场景下的热门算法题
    滑动窗口算法
    基于python的新生入学管理系统设计与实现-计算机毕业设计源码+LW文档
    基于搜索指数可视化分析近十年的中秋热度
    【Linux】进程间通信——进程间通信的介绍和分类、管道、匿名管道、命名管道、匿名管道与命名管道的区别
    MySQL 查询结果与分页 2022/09/08
    A - Penalty Kick
    【重识云原生】第六章容器6.1.7.2节——cgroups原理剖析
  • 原文地址:https://blog.csdn.net/w_pab/article/details/133523237