在这篇“Java 8新特性教程”系列文章中,我们会深入解释,并通过代码来展示,如何通过流来遍历集合,如何从集合和数组来创建流,以及怎么聚合流的值。
  在之前的文章“遍历、过滤、处理集合及使用Lambda表达式增强方法”中,我已经深入解释并演示了通过lambda表达式和方法引用来遍历集合,使用predicate接口来过滤集合,实现接口的默认方法,后还演示了接口静态方法的实现。
  源代码都在我的Github上:可以从 这里克隆。
  内容列表
  使用流来遍历集合。
  从集合或数组创建流。
  聚合流中的值。
  1. 使用流来遍历集合
  简介:
  Java的集合框架,如List和Map接口及Arraylist和HashMap类,让我们很容易地管理有序和无序集合。集合框架自引入的第起在 持续的改进。在Java SE 8中,我们可以通过流的API来管理、遍历和聚合集合。一个基于流的集合与输入输出流是不同的。
  如何工作?
  它采用一种全新的方式,将数据作为一个整体,而不是单独的个体来处理。当你使用流时,你不需要关心循环或遍历的细节。你可以直接从一个集合创建一个流。然 后你能用这个流来许多事件了,如遍历、过滤及聚和。我将从项目 Java8Features 的 com.tm.java8.features.stream.traversing 包下的例子开始。代码在一个SequentialStream 类中,Java SE 8 中有两种集合流,即串行流和并行流。
  List<person> people = new ArrayList<>();
  people.add(new Person("Mohamed", 69));
  people.add(new Person("Doaa", 25));
  people.add(new Person("Malik", 6));
  Predicate<person> pred = (p) -> p.getAge() > 65;
  displayPeople(people, pred);
  ...........
  private static void displayPeople(List<person> people, Predicate<person> pred) {
  System.out.println("Selected:");
  people.forEach(p -> {
  if (pred.test(p)) {
  System.out.println(p.getName());
  }
  });
  }
  在这两种流中,串行流相对比较简单,它类似一个迭代器,每次处理集合中的一个元素。但是语法与以前不同。在这段代码中,我创建了 pepole 的数组列表,向上转型为List。它包含三个 Person 类的实例。然后我们使用 Predicate 声明一个条件,只有满足这个条件的 people 才会显示。在 displayPeople() 方法的48到52行循环遍历该集合,挨个测试其中的每一项。运行这段代码,你将获得如下的结果:
  Selected:
  Mohamed
  我将会展示如何使用流来重构这段代码。首先,我注释了这段代码。然后,在这段注释的代码下,我开始使用集合对象 people。然后我调用一个 stream() 方法。一个stream对象,类似集合,也要声明泛型。如果你从一个集合获取流,则该流中每一项的类型与集合本身是一致的。我的集合是 Person 类的实例,所以流中也使用同样的泛型类型。
  System.out.println("Selected:");
  //        people.forEach(p -> {
  //            if (pred.test(p)) {
  //                System.out.println(p.getName());
  //            }
  //        });
  people.stream().forEach(p -> System.out.println(p.getName()));
  }
  你可以调用一个 stream() 方法来获得了一个流对象,然后可以在该对象上进行一些操作。我简单地调用了 forEach 方法,该方法需要一个Lamda表达式。我在参数中传递了一个Lamda表达式。列表中的每一项是通过迭代器处理的每一项。处理过程是通过Lambda 操作符和方法实现来完成的。我简单使用system output来输出每个人的名称。保存并运行这段代码,输出结果如下。因为没有过滤,所以输出了列表中所有元素。
  Selected:
  Mohamed
  Doaa
  Malik
  现在,一旦有了一个流对象,可以很容易使用 predicate 对象了。当使用 for each 方法处理每一项时,我不得不显示调用 predicate 的 test 方法,但是使用流时,你可以调用一个名为 filter 的方法。该方法接收一个 predicate 对象,所有的 predicate 对象都有一个 test 方法,所以它已经知道怎样去调用该方法。所以,我对该代码做一点改动。我将.forEach()方法下移了两行,然后在中间的空白行,我调用了 filter 方法。
  people.stream()
  .filter(pred)
  .forEach(p -> System.out.println(p.getName()));
  filter方法接收一个 predicate 接口的实例对象。我将 predicate 对象传进去。filtr 方法返回一个过滤后的流对象,在这个对象上我可以去调用forEach()方法了。我运行这段代码,这次我只显示集合中满足预定义条件的项了。你可以在 流对象上做更多的事情。去看看 Java SE 8 API 中流的doc文档吧。
  Selected:
  Mohamed
  你将会看到除了过滤,你还可以做聚合、排序等其他的事情。在我总结这段演示之前,我想向你们展示一下串行流和并行流之前的重要区别。Java SE 8 的一个重要目标是改善多 CPU 系统的处理能力。Java 可在运行期自动协调多个 CPU 的运行。你需要做的所有事情仅仅是将串行流转换为并行流。
  从语法上讲,有两种方法来实现流的转换。我复制一份串行流类。在包视图窗口,我复制并粘贴该类,然后对它重命名,ParallelStream,打开这个 新的类。在这个版本中,删除了注释的代码。我不再需要这些注释了。现在可以通过两种方式创建并行流。第一种方式是调用集合中的 parallelStream()方法。现在我拥有一个可以自动分配处理器的流了。
  private static void displayPeople(List<person> people, Predicate<person> pred) {
  System.out.println("Selected:");
  people.parallelStream()
  .filter(pred)
  .forEach(p -> System.out.println(p.getName()));
  }
  运行这段代码,可以看到完全一致的结果,过滤然后返回数据。
  Selected:
  Mohamed
  第二种创建并行流的方式。再次调用 stream() 方法,然后在 stream 方法的基础上调用 parallel() 方法,其本质上做的事情是一样的。开始是一个串行的流,然后再将其转换为并行流。但是它仍然是一个流。可以过滤,可以用之前的一样方式去处理。只是现在的 流可以分解到多个处理起来处理。
  people.stream()
  .parallel()
  .filter(pred)
  .forEach(p -> System.out.println(p.getName()));
  总结
  现在还没有一个明确的规定来说明在什么情况下并行流优于串行流。这个依赖于数据的大小和复杂性以及硬件的处理能力。还有你运行的多 CPU 系统。我可以给你的建议是测试你的应用和数据。建立一个基准的、计时的操作。然后分别使用串行流和并行流,看哪一个更适合于你。
  2、从集合或数组创建流
  简介
  Java SE 8’s stream API 是为了帮助管理数据集合而设计的,这些对象是指集合框架中的对象,例如数组列表或哈希表。但是,你也可以直接从数组创建流。
  如何工作?
  在 Java8Features 项目中的 eg.com.tm.java8.features.stream.creating 包下,我创建了一个名为ArrayToStream的类。在这个类的 main 方法中,我创建了一个包含三个元素的数组。每个元素都是Person类的一个实例对象。
  public static void main(String args[]) {
  Person[] people = {
  new Person("Mohamed", 69),
  new Person("Doaa", 25),
  new Person("Malik", 6)};
  for (int i = 0; i < people.length; i++) {
  System.out.println(people[i].getInfo());
  }
  }
  该类中为私有成员创建了 setters 和 getters 方法,以及 getInfo() 方法,该方法返回一个拼接的字符串。
  public String getInfo() {
  return name + " (" + age + ")";
  }
  现在,如果想使用流来处理这个数组,你可能认为需要先将数组转为数组列表,然后从这个列表创建流。但是,实际上你可以有两种方式直接从数组创建流。第一方式,我不需要处理数据的那三行代码,所以先注释掉。然后,在这个下面,我声明一个流类型的对象。
  Stream 是 java.util.stream 下的一个接口。当我按下 Ctrl+Space 并选取它的时候,会提示元素的泛型,这是流管理的类型。在这里,元素的类型即为Person,与数组元素本身的类型是一致的。我将我新的流对象命名为 stream,所有的字母都是小写的。这是第一种创建流的方法,使用流的接口,调用 of() 方法。注意,该方法存在两个不同版本。
  第一个是需要单个对象,第二个是需要多个对象。我使用一个参数的方法,所以传递一个名为 people 的数组,这是我需要做的所有事情。Stream.of() 意思是传入一个数组,然后将该数组包装在流中。现在,我可以使用 lambda 表达式、过滤、方法引用等流对象的方法。我将调用流的 for each 方法,并传入一个 lambda 表达式,将当前的 person 对象和 lambda 操作符后传入后,能获取到 person 对象的信息。该信息是通过对象的 getInfo() 方法获取到的。
  Person[] people = {
  new Person("Mohamed", 69),
  new Person("Doaa", 25),
  new Person("Malik", 6)};
  //        for (int i = 0; i < people.length; i++) {
  //            System.out.println(people[i].getInfo());
  //        }
  Stream<Person> stream = Stream.of(people);
  stream.forEach(p -> System.out.println(p.getInfo()));
  保存并运行这段代码,可获取到结果。输出的元素的顺序与我放入的顺序是一致的。这是第一种方式:使用 Stream.of() 方法。
  Mohamed (69)
  Doaa (25)
  Malik (6)
  另一种方式与上面的方式实际上是相同的。复制上面的代码,并注释掉第一种方式。这次不使用 Stream.of() 方法,我们使用名为 Arrays 的类,该类位于 java.util 包下。在这个类上,可以调用名为 stream 的方法。注意,stream 方法可以包装各种类型的数组,包括基本类型和复合类型。
  //      Stream<person> stream = Stream.of(people);
  Stream<person> stream = Arrays.stream(people);
  stream.forEach(p -> System.out.println(p.getInfo()));
  保存并运行上面的代码,流完成的事情与之前实质上是一致的。
  Mohamed (69)
  Doaa (25)
  Malik (6)
  结论
  所以,无论是 Stream.of() 还是 Arrays.stream(),所做的事情实质上是一样的。都是从一个基本类型或者复合对象类型的数组转换为流对象,然后可以使用 lambda 表达式、过滤、方法引用等功能了。
  3、聚合流的值
  简介
  之前,我已经描述过怎么使用一个流来迭代一个集合。你也可以使用流来聚合集合中的每一项。如计算总和、平均值、总数等等。当你做这些操作的时候,弄明白并行流特性非常重要。
  如何工作?
  我会在 Java8Features 项目的 eg.com.tm.java8.features.stream.aggregating 包下进行演示。首先我们使用 ParallelStreams 类。在这个类的 main 方法中,我创建了一个包含字符串元素的数组列表。我简单地使用循环在列表中添加了10000个元素。然后在35和36行,我创建了一个流对象,并通过 for each 方法挨个输出流中每一项。