Java8 教程

Java8 收集结果

当你处理完流之后,你通常只是想查看一下结果,而不是将它们聚合为一个值。你可以调用 iterator 方法来生成一个传统风格的迭代器,用于访问元素。你也可以调用 toArray 方法获得含有流中所有元素的一个数组。

由于无法在运行时来创建一个泛型数组,所以表达式 stream.toArray() 会返回一个 Object[] 数组。如果你希望得到一个正确类型的数组,可以将类型传递给数组的构造函数。例如:

package com.hxstrive.jdk8.stream_api;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * 收集结果
 * @author hxstrive.com
 */
public class StreamCollectDemo {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("one", "two", "three");

        Iterator<String> iterator = list.stream().map(String::toUpperCase).iterator();
        while(iterator.hasNext()) {
            System.out.println(iterator.next());
        }
        //输出:
        //ONE
        //TWO
        //THREE

        Object[] objects = list.stream().map(String::toUpperCase).toArray();
        System.out.println(Arrays.toString(objects));
        //输出:[ONE, TWO, THREE]

        String[] strings = list.stream().map(String::toUpperCase).toArray(String[]::new);
        System.out.println(Arrays.toString(strings));
        //输出:[ONE, TWO, THREE]
    }

}

现在假设你希望将结果收集到一个 HashSet 中。如果这个过程是并行的,那么你不能直接将元素放到一个单独的 Hashset 中,因为 HashSet 对象不是线程安全的,也正因此你不能使用聚合函数。并行收集中的每一段都需要从其所属的空 HashSet 开始,而聚合函数只能允许你提供一个标识值。因此,我们需要使用 collect 方法,它接受三个参数:

(1)一个能创建目标类型实例的方法,例如 HashSet 的构造函数。

(2)一个将元素添加到目标中的方法,例如一个 add 方法。

(3)一个将两个对象整合到一起的方法,例如 addAll 方法。

注意:目标对象不一定是集合。它可以是一个 StringBuilder 对象或者一个可以记录个数和总和的对象。

下面是如何使用 HashSet 的 collect 方法示例:

package com.hxstrive.jdk8.stream_api;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

/**
 * 收集结果
 * @author hxstrive.com
 */
public class StreamCollectDemo2 {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("one", "two", "three");

        // 使用 collect 收集结果
        HashSet<String> hashSet = list.stream().map(String::toUpperCase)
                .collect(HashSet::new,HashSet::add, HashSet::addAll);
        System.out.println(hashSet); // [ONE, TWO, THREE]
    }

}

下面将使用 collect 将结果收集到 StringBuffer 中:

List<String> list = Arrays.asList("one", "two", "three");

// 使用 collect 收集结果
StringBuffer buffer = list.stream().map(String::toUpperCase)
        .collect(StringBuffer::new, StringBuffer::append, StringBuffer::append);
System.out.println(buffer); // ONETWOTHREE

在实际中,你不需要这么做,因为 Collector 接口已经为我们提供了这三个方法,并且 Collectors 类还为常用的收集类型提供了各个工厂方法。要将一个流收集到一个 list 或者 set 中,你只需要调用:

List<String> result = stream.collect(Collectors.toList());

或者

Set<String> result = stream.collect(Collectors.toSet());

如果你希望控制得到的 set 类型,可以使用如下方式的调用:

TreeSet<String> result = stream.collect(Collectors.toCollection(TreeSet::new));

假设你希望将流中的所有字符串连接并收集起来,你可以调用:

String result = stream.collect(Collectors.joining());

如果你希望在这些元素中间添加一个分隔符,那么就将分隔符传递给 joining 方法:

String result = stream.collect(Collectors.joining(","));

如果你的流包含字符串以外的对象,你需要首先将它们转换为字符串,如下所示:

String result = stream.map(Object::toString).collect(Collectors.joining(","));

如果你希望将流的结果聚合为一个总和、平均值、最大值或者最小值,那么请使用 summarizing(int|Long IDouble) 方法中的一种。这些方法会接受一个将流对象映射为一个数字的函数,并产生一个 (Int|LongIDouble) Summarystatistics 类型的结果,其中包含了获取总和、平均值、最大值和最小值的方法。例如:

package com.hxstrive.jdk8.stream_api;

import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 收集结果
 * @author hxstrive.com
 */
public class StreamCollectDemo4 {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("one", "two", "three");

        IntSummaryStatistics summary = list.stream().collect(Collectors.summarizingInt(String::length));
        System.out.println("count = " +  summary.getCount());
        System.out.println("avg = " +  summary.getAverage());
        System.out.println("max = " +  summary.getMax());
        System.out.println("min = " +  summary.getMin());
        System.out.println("sum = " +  summary.getSum());
        //输出:
        //count = 3
        //avg = 3.6666666666666665
        //max = 5
        //min = 3
        //sum = 11
    }

}

注意:到目前为止,你已经了解了如何聚合或收集流的值。但是也许你只需要将它们打印出来,或者将它们存到一个数据库中,那么你可以使用 forEach 方法,如下。

stream.forEach(System.out::println);

你向该方法传递的函数会被应用到流中的每个元素上。在一个并行流上,确保该函数是线程安全的,可以被并发执行是你的职责。

在一个并行流上,可能会以任意顺序来访问元素。如果你希望按照流的顺序来执行它们,那么请调用 forEachordered 方法。当然,这样做你可能会放弃大多数并行计算所能带来的好处。

forEach 和 forEachordered 方法都是终止操作。因此在调用它们之后,你就不能再使用这个流了。如果你希望还能继续使用这个流,请使用 peek 方法。例如:

package com.hxstrive.jdk8.stream_api;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 收集结果
 * @author hxstrive.com
 */
public class StreamCollectDemo5 {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("one", "two", "three");

        // peek 操作后,流还可继续操作
        List<Integer> lengths = new ArrayList<>();
        String[] strings = list.stream().peek(s -> {
            lengths.add(s.length());
        }).map(String::toUpperCase).toArray(String[]::new);

        System.out.println(Arrays.toString(lengths.toArray()));
        System.out.println(Arrays.toString(strings));
        //输出:
        //[3, 3, 5]
        //[ONE, TWO, THREE]

        //同步
        list.forEach(s -> {
            System.out.println(Thread.currentThread().getName() + " = " + s);
        });
        //输出:
        //main = one
        //main = two
        //main = three

        //异步
        list.parallelStream().forEach(s -> {
            System.out.println(Thread.currentThread().getName() + " = " + s);
        });
        //输出:
        //main = two
        //main = three
        //ForkJoinPool.commonPool-worker-1 = one

        list.parallelStream().forEachOrdered(s -> {
            System.out.println(Thread.currentThread().getName() + " = " + s);
        });
        //输出:
        //ForkJoinPool.commonPool-worker-1 = one
        //ForkJoinPool.commonPool-worker-1 = two
        //ForkJoinPool.commonPool-worker-1 = three
    }

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