Java Stream API 提供了一种更具函数式编程风格的方式,用于迭代和处理集合等中的元素。该 API 于 Java 8 中引入,其设计初衷是与 Java lambda 表达式配合使用。本文中的多个示例都会用到 lambda 表达式,因此若你尚不了解这一特性,建议在阅读本文前先进行学习。
通过调用给定集合的 stream() 方法,可以从集合中获取一个流。例如:
List<String> items = new ArrayList<String>(); items.add("one"); items.add("two"); items.add("three"); Stream<String> stream = items.stream();
首先,创建一个字符串列表,并向其中添加三个字符串。然后,通过调用 items.stream() 方法获取一个字符串流。这类似于通过调用 items.iterator() 方法获取迭代器(Iterator),但流(Stream)和迭代器(Iterator)是截然不同的。
一旦你从一个 Collection 实例中获取到一个 Stream 实例,你就可以使用该流来处理集合中的元素。
处理流中的元素分为两个步骤/阶段:
(1)配置:首先需对流进行配置,配置内容可包含过滤器、映射等,这些操作也被称为非终端操作。
(2)处理:其次,需要对该流进行处理。处理环节包括对经过过滤和映射的对象执行特定操作。在配置调用阶段不会进行任何实际处理,只有当对流调用处理方法时,处理才会启动。流的处理方法也被称为终端操作。
你可以使用 filter() 方法对流进行过滤。例如:
List<String> list = new ArrayList<>(); list.add("one"); list.add("two"); list.add("three"); System.out.println(list); //[one, two, three] list.stream().filter(new Predicate<String>() { @Override public boolean test(String s) { return s.startsWith("t"); } }).forEach(System.out::println);
或
list.stream().filter(s -> { return s.startsWith("t"); }).forEach(System.out::println);
运行结果:
[one, two, three] two three
filter() 方法以 Predicate 作为参数,而 Predicate 接口中包含 test () 函数,作为参数传入的 lambda 表达式会与该函数相匹配。换而言之,此 lambda 表达式实现了 Predicate.test () 方法。
test() 方法的定义如下:
boolean test(T t)
它接受一个参数并返回一个布尔值。如果你看上面的 lambda 表达式,会发现它接受一个参数 s,并返回一个布尔值 —— 即 s.startsWith("t") 方法调用的结果。
当你对一个 Stream 调用 filter() 方法时,作为参数传递给 filter() 方法的过滤器会被内部存储起来。此时还不会进行任何过滤操作。
传递给 filter() 函数的参数决定了流中的哪些元素应该被处理,哪些应该被排除在处理之外。如果传递给filter() 的参数的 Predicate.test() 方法对某个元素返回 true,那就意味着该元素应该被处理。如果返回 false,该元素则不会被处理。
你可以将集合中的元素映射为其他类型的对象。具体来说,对于集合中的每一个元素,都能基于该元素创建一个新对象,而具体的映射规则可由你自行定义。例如:
List<String> list = new ArrayList<>(); list.add("one"); list.add("two"); list.add("three"); System.out.println(list); //[one, two, three] list.stream().map(e -> { return e.length(); }).forEach(e -> { System.out.println(e); });
上述示例,将计算 list 集合中的每个字符串元素的长度并返回,然后通过 forEach 输出每个字符串的长度到控制台。
注意,上述示例如果没有使用 forEach 方法打印结果,则示例实际并不会执行映射操作。它只是为映射配置了流。一旦调用了某个流处理方法,映射(和过滤)操作才会执行,如 forEach 方法。
collect () 方法是 Stream 接口中常用的流处理方法之一,调用该方法时,会触发此前配置的过滤与映射操作,并对这些操作生成的对象进行收集。例如:
List<String> list = new ArrayList<>(); list.add("one"); list.add("two"); list.add("three"); System.out.println(list); //[one, two, three] List<String> result = list.stream().filter(s -> { return s.startsWith("t"); }).collect(Collectors.toList()); System.out.println(result); //[two, three]
本示例先创建一个流,接着添加过滤器(该过滤器仅接受以字符 “t” 开头的字符串),最后将所有通过过滤器的对象收集到一个列表中。因此,最终得到的列表包含了 list 集合里所有以 “t” 开头的字符串,打印到控制台。
min() 和 max() 方法均为流处理方法。调用这些方法后,流会被迭代,过滤与映射操作会随之应用,最终返回流中的最小值或最大值。例如:
List<String> list = new ArrayList<>(); list.add("one"); list.add("two"); list.add("three"); System.out.println(list); //[one, two, three] String result = list.stream() .min(Comparator.comparing(item -> item.length())) .get(); System.out.println(result); //one
或者
List<String> list = new ArrayList<>(); list.add("one"); list.add("two"); list.add("three"); System.out.println(list); //[one, two, three] String result = list.stream() .max(Comparator.comparing(item -> item.length())) .get(); System.out.println(result); //three
调用 min() 或 max() 方法后,会得到一个 Optional 实例,该实例自带 get() 方法用于获取值。若流为空(即无元素),则 get() 方法将返回 null。
min() 和 max() 方法以 Comparator 作为参数,通过调用 Comparator.comparing () 方法,并传入 lambda 表达式,即可创建出所需的 Comparator。需要注意的是,comparing() 方法实际接收的是 Function 类型参数,这是一种适配 lambda 表达式的函数式接口,其核心功能是接收一个参数并返回对应的值。
count() 方法只是返回经过过滤后流中的元素数量。例如:
List<String> list = new ArrayList<>(); list.add("one"); list.add("two"); list.add("three"); System.out.println(list); //[one, two, three] long result = list.stream() .filter(s -> { return s.startsWith("t"); }) .count(); System.out.println(result); //2
上面示例会迭代流,并保留所有以字符 t 开头的元素,然后对这些元素进行计数。注意,count() 方法返回一个 long 类型的值,该值是流经过过滤等操作后元素的数量。
reduce() 方法可以将流中的元素缩减为单个值。例如:
List<String> list = new ArrayList<>(); list.add("one"); list.add("two"); list.add("three"); System.out.println(list); //[one, two, three] String result = list.stream() .reduce((item1, item2) -> { return item1 + "-" + item2; }) .get(); System.out.println(result); //one-two-three
reduce() 方法接收一个 BinaryOperator 作为参数,该参数可以通过 lambda 表达式轻松实现。上面的lambda 表达式实现的是 BinaryOperator.apply() 方法。此方法接收两个参数:一个是 item1,即累加值;另一个是 item2,即流中的一个元素。因此,reduce() 函数生成的值是处理完流中最后一个元素后的累加值。在上面的示例中,每个项目都会拼接到累加值上。这是通过实现 BinaryOperator 的 lambda 表达式来完成的。
接收 BinaryOperator 作为参数的 reduce() 方法会返回一个 Optional。如果流中不包含任何元素, Optional.get() 会返回 null。否则,它会返回 reduce() 后的值。
还有另一个 reduce() 方法,它接收两个参数。一个是累加值的初始值,另一个是 BinaryOperator。例如:
List<String> list = new ArrayList<>(); list.add("one"); list.add("two"); list.add("three"); System.out.println(list); //[one, two, three] String result = list.stream() .reduce(">>", (item1, item2) -> { return item1 + "-" + item2; }); System.out.println(result); //>>-one-two-three
这个示例将“>>”字符串作为初始值,然后使用与前一个示例相同的 lambda 表达式。这个版本的reduce() 方法直接返回累加后的值,而不是一个 Optional。如果流中不包含任何元素,将返回初始值。例如:
List<String> list = new ArrayList<>(); String result = list.stream() .reduce(">>", (item1, item2) -> { return item1 + "-" + item2; }); System.out.println(result); //>>
上述示例 list 为空,没有任何元素,reduce() 方法将返回初始值“>>”。