5.2 流的切片

本节会讨论如何通过其他方式选择或跳过流中的某些元素。使用Stream的一些操作结合谓词,你可以高效地选择或者丢弃流中的元素,譬如忽略流的前几个元素,或者按照设定的大小对流实施截短操作。

5.2.1 使用谓词对流进行切片

Java 9引入了两个新方法,可以高效地选择流中的元素,这两个方法分别是:takeWhiledropWhile

  • 使用takeWhile

假设你需要处理下面这个菜单列表:

  1. List<Dish> specialMenu = Arrays.asList(
  2. new Dish("seasonal fruit", true, 120, Dish.Type.OTHER),
  3. new Dish("prawns", false, 300, Dish.Type.FISH),
  4. new Dish("rice", true, 350, Dish.Type.OTHER),
  5. new Dish("chicken", false, 400, Dish.Type.MEAT),
  6. new Dish("french fries", true, 530, Dish.Type.OTHER));

怎样才能从这些菜单中选出热量少于320卡路里的那些菜肴呢?你本能地想起了前面章节学习过的filter操作,它可以执行下面的动作:

  1. List<Dish> filteredMenu
  2. = specialMenu.stream()
  3. .filter(dish -> dish.getCalories() < 320)
  4. .collect(toList()); ←---- 由季节性的水果、虾构成的列表

然而,采用这种方式,初始列表中的元素已经按照热量进行了排序操作!这里采用filter的缺点是,你需要遍历整个流中的数据,对其中的每一个元素执行谓词操作。而你本可以在发现第一个热量大于(或者等于)320卡路里的菜肴时就停止处理的。如果你要处理的列表规模不大,这不算什么大问题,但是,如果你要处理的是一个由海量元素构成的流,采用恰当的方式所带来的性能提升还是很可观的。然而,怎样才能达到期望的效果呢?takeWhile操作就是为此而生的!它可以帮助你利用谓词对流进行分片(即便你要处理的流是无限流也毫无困难)。更妙的是,它会在遭遇第一个不符合要求的元素时停止处理。下面这段代码演示了如何使用takeWhile

  1. List<Dish> slicedMenu1
  2. = specialMenu.stream()
  3. .takeWhile(dish -> dish.getCalories() < 320)
  4. .collect(toList()); ←---- 由季节性的水果、虾构成的列表
  • 使用dropWhile

如果你想要的是其他的元素,又该怎么办呢?譬如,你想要找出那些热量大于320卡路里的元素。你可以借助dropWhile操作达到这一目标:

  1. List<Dish> slicedMenu2
  2. = specialMenu.stream()
  3. .dropWhile(dish -> dish.getCalories() < 320)
  4. .collect(toList()); ←---- 由米饭、鸡肉以及炸薯条构成的列表

dropWhile操作是对takeWhile操作的补充。它会从头开始,丢弃所有谓词结果为false的元素。一旦遭遇谓词计算的结果为true,它就停止处理,并返回所有剩余的元素,即便要处理的对象是一个由无限数量元素构成的流,它也能工作得很好。

5.2.2 截短流

流支持limit(n)方法,该方法会返回另一个不超过给定长度的流。所需的长度作为参数传递给limit。如果流是有序的,则最多会返回前n个元素。比如,你可以建立一个List,选出热量超过300卡路里的头三道菜:

  1. List<Dish> dishes = specialMenu
  2. .stream()
  3. .filter(dish -> dish.getCalories() > 300)
  4. .limit(3)
  5. .collect(toList()); ←---- 列出米饭、鸡肉、炸薯条

图5-3展示了filterlimit的组合。你可以看到,该方法只选出了符合谓词的头三个元素,然后就立即返回了结果。

5.2 流的切片 - 图1

图 5-3 截短流

请注意,limit也可以用在无序流上,比如源是一个Set。这种情况下,limit的结果不会以任何顺序排列。

5.2.3 跳过元素

流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。请注意,limit(n)skip(n)是互补的!例如,下面的代码将跳过热量超过300卡路里的头两道菜,并返回剩下的。图5-4展示了这个查询。

  1. List<Dish> dishes = menu.stream()
  2. .filter(d -> d.getCalories() > 300)
  3. .skip(2)
  4. .collect(toList());

5.2 流的切片 - 图2

图 5-4 在流中跳过元素