Java 集合:集合和流

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)处理:其次,需要对该流进行处理。处理环节包括对经过过滤和映射的对象执行特定操作。在配置调用阶段不会进行任何实际处理,只有当对流调用处理方法时,处理才会启动。流的处理方法也被称为终端操作。

  

Stream.filter()

你可以使用 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,该元素则不会被处理。

  

Stream.map()

你可以将集合中的元素映射为其他类型的对象。具体来说,对于集合中的每一个元素,都能基于该元素创建一个新对象,而具体的映射规则可由你自行定义。例如:

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 方法。

  

Stream.collect()

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” 开头的字符串,打印到控制台。

  

Stream.min() 和 Stream.max()

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 表达式的函数式接口,其核心功能是接收一个参数并返回对应的值。

  

Stream.count()

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 类型的值,该值是流经过过滤等操作后元素的数量。

  

Stream.reduce()

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() 方法将返回初始值“>>”。

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号