JDK Stream流使用介绍

       Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,大批量数据操作。通常我们需要多行代码才能完成的操作,借助于Stream流式处理可以很简单的实现。

       Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的一个对象,它更像一个高级版本的Iterator。同时Stream提供串行和并行两种模式进行汇聚操作。比如你的Stream里面有很多数据,Stream可以开多个线程每个线程处理一部分。最后把结果汇总起来。

       在开始之前我们先用一个图来整体的概况下Stream。如下所示:

       

一 Stream流创建

       想使用Stream流,首先咱得先创建一个Stream流对象。创建Steam需要数据源.这些数据源可以是集合、可以是数组、可以使文件、甚至是你可以去自定义等等。

1.1 集合作为Stream数据源

       集合Collection作为Stream的数据源,应该也是我们用的最多的一种数据源了。Collection里面也提供了一些方法帮助我们把集合Collection转换成Stream。

1.1.1 stream()方法

       调用Collection.stream()函数创建一个Stream对象。相当于把集合Collection里面的数据都导入到了Stream里面去了。

    @Test
    public void collectionStream() {

        List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5);
        // 使用List创建一个流对象
        Stream<Integer> stream = list.stream();
        // TODO: 对流对象做处理
    }

1.1.2 parallelStream()方法

       调用Collection.parallelStream()创建Stream对象。

    @Test
    public void collectionParallelStream() {

        List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5);
        // 使用List创建一个流对象
        Stream<Integer> stream = list.parallelStream();
        // TODO: 对流对象做处理
    }

parallelStream()与stream()区别是parallelStream()使用多线程并发处理最后汇总结果,而stream()是单线程。所以相对来说parallelStream()效率要稍微高点。

1.2 数组作为Stream数据源

       数组也可以作为Stream的数据源。我们可以通过Arrays.stream()方法把一个数组转化成流对象。Arrays.stream()方法很丰富,有很多个。大家可以根据实际情况使用。

    @Test
    public void arrayStream() {

        int[] intArray = new int[10];
        for (int index = 0; index < intArray.length; index++) {
            intArray[index] = index;
        }
        // 使用数组创建一个流对象
        IntStream stream = Arrays.stream(intArray);
        // TODO: 对流对象做处理
    }

1.3 BufferedReader作为Stream数据源

       我们也可以把BufferedReader里面lines方法把BufferedReader里面每一行的数据作为数据源生成一个Stream对象。

    @Test
    public void bufferedReaderStream() {

        File file = new File("/home/tuacy/github/google-guava-study/src/main/resources/application.yml");
        try {
            // 把文件里面的内容一行一行的读出来
            BufferedReader in = new BufferedReader(new FileReader(file));
            // 生成一个Stream对象
            Stream<String> stream = in.lines();
            // TODO: 对流对象做处理
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

1.4 File作为Stream数据源

       Files里面多个生成Stream对象的方法,都是对Path(文件)的操作。有的是指定Path目录下所有的子文件(所有的子文件相当于是一个列表了)作为Stream数据源,有的把指定Path文件里面的每一行数据作为Stream的数据源。

1.4.1 Files.list()

       列出指定Path下面的所有文件。把这些文件作为Stream数据源。

    @Test
    public void fileListStream() {
        Path path = Paths.get("D:\\job\\git\\google-guava-study\\src\\main\\resources");
        try {
            // 找到指定path下的所有的文件
            Stream<Path> stream = Files.list(path);
            // TODO: 对流对象做处理
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

1.4.2 Files.walk()

       Files.walk()方法用于遍历子文件(包括文件夹)。参数maxDepth用于指定遍历的深度。把子文件(子文件夹)作为Stream数据源。

    @Test
    public void fileWalkStream() {
        Path path = Paths.get("D:\\job\\git\\google-guava-study\\src\\main\\resources");
        try {
            // 第二个参数用于指定遍历几层
            Stream<Path> stream = Files.walk(path, 2);
            // TODO: 对流对象做处理
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

1.4.3 Files.find()

       Files.find方法用于遍历查找(过滤)子文件。参数里面会指定查询(过滤)条件。把过滤出来的子文件作为Stream的数据源。

    @Test
    public void fileFindStream() {
        Path path = Paths.get("D:\\job\\git\\google-guava-study\\src\\main\\resources");
        try {
            // 找到指定path下的所有不是目录的文件
           Stream<Path> stream = Files.find(path, 2, (path1, basicFileAttributes) -> {
               // 过滤掉目录文件
               return !basicFileAttributes.isDirectory();
           });
            // TODO: 对流对象做处理
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

1.4.4 Files.lines()

       Files.lines方法是把指定Path文件里面的每一行内容作为Stream的数据源。

    @Test
    public void fileLineStream() {
        Path path = Paths.get("D:\\job\\git\\google-guava-study\\src\\main\\resources\\application.yml");
        try {
            // 生成一个Stream对象
            Stream<String> stream = Files.lines(path);
            // TODO: 对流对象做处理
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

1.5 自己构建Stream

       我们也可以自己去创建Stream自己提供数据源。Stream类里面提供of()、iterate()、generate()、builder()等一些方法来创建Stream,Stream的数据源我们自己提供。

1.5.1 Stream.of()

       Stream.of()函数参数就是数据源。

       Stream<Integer> ofSteam = Stream.of(1,2,3,4,5,6);

1.5.2 Stream.iterate()

       Stream.iterate()可以用来生成无限流,函数需要两个参数:第一个参数是初始值、第二个参数用于确定怎么根据前一个元素的值生成下一个元素。

        // Stream.iterate() 流式迭代器 Stream.iterate()函数的第二个参数告诉你怎么去生成下一个元素
        Stream<BigInteger> integers = Stream.iterate(
                BigInteger.ONE,
                new UnaryOperator<BigInteger>() {

                    @Override
                    public BigInteger apply(BigInteger bigInteger) {
                        return bigInteger.add(BigInteger.ONE);
                    }
                });
        // 简单输出
        integers.limit(10).forEach(new Consumer<BigInteger>() {
            @Override
            public void accept(BigInteger bigInteger) {
                System.out.println(bigInteger.intValue());
            }
        });

1.5.3 Stream.generate()

       Stream.generate()也是用于生成一个无限流。参数用于获取每个元素。

        // Stream.generate() 生成无限流
        Stream<Double> generateA = Stream.generate(new Supplier<Double>() {
            @Override
            public Double get() {
                return java.lang.Math.random() * 100;
            }
        });
        // 简单输出前10个值
        generateA.limit(10).forEach(new Consumer<Double>() {
            @Override
            public void accept(Double bigInteger) {
                System.out.println(bigInteger.intValue());
            }
        });

1.5.4 Stream.build()

       Stream.build()通过建造者模式生成一个Stream建造器。然后把需要加入Stream里面的数据源一个一个通过建造器添加进去。

        // Stream.builder()构造一个Stream对象
        Stream.Builder<Integer> build = Stream.<Integer>builder().add(1)
                .add(2)
                .add(3);
        build.accept(4);
        build.accept(5);
        build.build().forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
        // TODO: 对流对象做处理

1.6 其他Stream创建方式

       Stream其他创建方式我们就不一一举例了。有如下方式。

  • Random.ints()。
  • BitSet.stream()。
  • Pattern.splitAsStream(java.lang.CharSequence)
  • JarFile.stream()。
  • ...

二 Stream流操作(中间操作符)

什么是操作符?操作符就是对数据进行的一种处理工作,一道加工程序;就好像工厂的工人对流水线上的产品进行一道加工程序一样。

       Stream流操作就是对Stream流的各种处理。Stream里面已经给提供了很多中间操作(我们一般称之为操作符)。

Stream提供的流操作符。

Stream流操作符 解释
filter 过滤
map 对流里面每个元素做转换
mapToInt 把流里面的每个元素转换成int
mapToLong 流里面每个元素转换成long
mapToDouble 流里面每个元素转换成double
flatMap 流里面每个元素转换成Steam对象,最后平铺成一个Stream对象
flatMapToInt 流里面每个元素转换成IntStream对象,最后平铺成一个IntStream对象
flatMapToLong 流里面每个元素转换成LongStream对象,最后平铺成一个LongStream对象
flatMapToDouble 流里面每个元素转换成DoubleStream对象,最后平铺成一个DoubleStream对象
distinct 去重
sorted 排序
peek 查看流里面的每个元素
limit 返回前n个数
skip 跳过前n个元素

       Stream提供了这么多的操作符,而且这些操作符是可以组合起来使用。关于每个操作符的使用我们用一个简单的实例代码来说明。

2.1 filter

       filter用于对流里面的数据做过滤操作。

    // 过滤
    @Test
    public void filter() {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        // 过滤出偶数
        Stream<Integer> filterStream = stream.filter(new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer % 2 == 0;
            }
        });
        // 简单输出
        filterStream.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
    }

2.2 map

       map用于对流里面的元素做转换。

    // 转换
    @Test
    public void map() {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        // 整数转换为String
        Stream<String> mapStream = stream.map(new Function<Integer, String>(){
            @Override
            public String apply(Integer integer) {
                return String.valueOf(integer);
            }
        });
        // 简单输出
        mapStream.forEach(new Consumer<String>() {
            @Override
            public void accept(String integer) {
                System.out.println(integer);
            }
        });
    }

2.3 mapToInt、mapToLong、mapToDouble

       mapToInt、mapToLong、mapToDouble用于将流里面的元素转换成对应的类型。

    // 转换
    @Test
    public void mapToInt() {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        // 整数转换为String
        IntStream mapStream = stream.mapToInt(new ToIntFunction<Integer>(){

            @Override
            public int applyAsInt(Integer value) {
                return value == null ? 0 : value;
            }
        });
        // 简单输出总和
        System.out.println(mapStream.sum());
    }

2.4 flatMap、flatMapToInt、flatMapToLong、flatMapToDouble

       flatMap、flatMapToInt、flatMapToLong、flatMapToDouble也是对每个元素的转换,不过他们和map的不同点在于,他们是吧每个元素转换成一个Stream流,最终在平铺成一个Stream流。

    // 转换
    @Test
    public void flatMap() {
        Stream<String> stream = Stream.of("java:1", "android:2", "ios:3");
        // 整数转换为String
        Stream<String> rerStream = stream.flatMap(
                new Function<String, Stream<String>>() {
                    @Override
                    public Stream<String> apply(String s) {
                        // 分割
                        Iterable<String> iterableList = Splitter.on(':').trimResults() // 移除前面和后面的空白
                                .omitEmptyStrings()
                                .split(s);
                        return Lists.newArrayList(iterableList).parallelStream();
                    }
                });
        // 简单输出
        rerStream.forEach(new Consumer<String>() {
            @Override
            public void accept(String integer) {
                System.out.println(integer);
            }
        });
    }
    // 转换
    @Test
    public void reduce() {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        // 所有的元素相加,在加上20
        Integer reduceValue = stream.reduce(20, new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer integer, Integer integer2) {
                System.out.println(integer);
                System.out.println(integer2);
                return integer + integer2;
            }
        });
        System.out.println(reduceValue);
    }

2.5 distinct

       distinct操作符用于去重。

    // 去重
    @Test
    public void distinct() {
        Stream<Integer> stream = Stream.of(1,2,3,1,2,3,1,2,3);
        Stream<Integer> rerStream = stream.distinct();
        // 简单输出
        rerStream.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
    }
    distinct根据某个字段去重。

2.6 sorted

       sorted操作符用于对流里面的元素排序。

    // 排序
    @Test
    public void sorted() {
        Stream<Integer> stream = Stream.of(1,2,3,2,5,4,8,6);
        Stream<Integer> rerStream = stream.sorted(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                if (o1.equals(o2)) {
                    return 0;
                } else {
                    return o1 > o2 ? 1 : -1;
                }
            }
        });
        // 简单输出
        rerStream.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
    }

2.7 peek

       peek操作符用于查看流里面的每个元素。在多个操作符同时使用的时候的中间加入peek操作符可以参考每个操作符之后的结果。

    // 查看
    @Test
    public void peek() {
        Stream<Integer> stream = Stream.of(1,2,3,1,2,3,1,2,3);
        // 查看
        Stream<Integer> reStream = stream.peek(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
        // 简单输出
        reStream.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
    }

2.8 limit

       limit操作符用于取流前面多少个元素。

    // limit
    @Test
    public void limit() {
        Stream<Integer> stream = Stream.of(1,2,3,1,2,3,1,2,3);
        // limit
        Stream<Integer> reStream = stream.limit(3);
        // 简单输出
        reStream.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
    }

2.9 skip

       skip用于跳过里面的多少个元素。

    // skip
    @Test
    public void skip() {
        Stream<Integer> stream = Stream.of(1,2,3,1,2,3,1,2,3);
        // skip
        Stream<Integer> reStream = stream.skip(3);
        // 简单输出
        reStream.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
    }

三. Stream流终端操作(终端操作符)

       Stream流终端操作是流式处理的最后一步,之前已经对Stream做了一系列的处理之后。该拿出结果了。我们可以在终端操作中实现对流的遍历、查找、归约、收集等等一系列的操作。

Stream流终端操作提供的函数有

终端操作符 解释
forEach 遍历
forEachOrdered 如果流里面的元素是有顺序的则按顺序遍历
toArray 转换成数组
reduce 归约 - 根据一定的规则将Stream中的元素进行计算后返回一个唯一的值
collect 收集 - 对处理结果的封装
min 最小值
max 最大值
count 元素的个数
anyMatch 任何一个匹配到了就返回true
allMatch 所有都匹配上了就返回true
noneMatch 没有一个匹配上就返回true
findFirst 返回满足条件的第一个元素
findAny 返回某个元素

       关于Stream终端操作部分,我们就着重讲下collect()函数的使用。因为其他的终端操作符都很好理解。collect()稍稍复杂一点。

3.1 collect()

       collect()的使用主要在于对参数的理解,所有我们这里要专门讲下collect()函数的参数Collector这个类,以及怎么去构建Collector对象。只有在了解了这些之后,咱们才可以熟练的把他们用在各种场景中。

3.1.1 Collector

       Collector类目前没别的用处,就是专门用来作为Stream的collect()方法的参数的。把Stream里面的数据转换成我们最终想要的结果上。

Collector各个方法,以及每个泛型的介绍

/**
 * Collector是专门用来作为Stream的collect方法的参数的
 *
 * 泛型含义
 * T:是流中要收集的对象的泛型
 * A:是累加器的类型,累加器是在收集过程中用于累积部分结果的对象。
 * R:是收集操作得到的对象(通常但不一定是集合)的类型。
 */
public interface Collector<T, A, R> {
    /**
     * 生成结果容器,容器类型为A
     * (多线程的情况下可能会调用多次,开多个线程同时去处理一个流,每个线程调用一次)
     */
    Supplier<A> supplier();

    /**
     * A对应supplier()函数创建的结果容器
     * T对应Stream流里面一个一个的元素
     * 用于消费元素,也就是归纳元素,一般在这个里面把流里面的元素T(也可以转换下)放到supplier()创建的结果T里面去
     */
    BiConsumer<A, T> accumulator();

    /**
     * 用于两个两个合并并行执行的线程的执行结果,将其合并为一个最终结果A
     * 多线程的情况下,多个线程并行执行。每个线程产生一个结果
     */
    BinaryOperator<A> combiner();

    /**
     * 用于将之前整合完的结果A转换成为R
     *
     * combiner()完成之后了A, 这里还可以在转一道。生成你自己想要的结果
     */
    Function<A, R> finisher();

    /**
     * characteristics表示当前Collector的特征值,
     * 这是个不可变Set
     * 它定义了收集器的行为--尤其是关于流是否可以多线程并行执行,以及可以使用哪些优化的提示
     */
    Set<Characteristics> characteristics();

    /**
     * 它定义了收集器的行为--尤其是关于流是否可以并行归约,以及可以使用哪些优化的提示
     */
    enum Characteristics {
        /**
         * accumulator函数可以从多个线程同时调用,且该收集器可以并行归约流。如果收集器没有标为UNORDERED,
         * 那它仅在用于无序数据源时才可以并行归约
         * 多线程并行
         */
        CONCURRENT,

        /**
         * 归约结果不受流中项目的遍历和累积顺序的影响(无序)
         */
        UNORDERED,

        /**
         * 无需转换结果
         */
        IDENTITY_FINISH
    }


    /**
     * 四参方法,用于生成一个Collector,T代表流中的一个一个元素,R代表最终的结果
     */
    public static<T, R> Collector<T, R, R> of(Supplier<R> supplier,
                                              BiConsumer<R, T> accumulator,
                                              BinaryOperator<R> combiner,
                                              Characteristics... characteristics);

    /**
     * 五参方法,用于生成一个Collector,T代表流中的一个一个元素,A代表中间结果,R代表最终结果,finisher用于将A转换为R
     */
    public static<T, A, R> Collector<T, A, R> of(Supplier<A> supplier,
                                                 BiConsumer<A, T> accumulator,
                                                 BinaryOperator<A> combiner,
                                                 Function<A, R> finisher,
                                                 Characteristics... characteristics);



}

       有了上面的介绍,接下来我们自己来new一个Collector对象,把我们Steam流里面的数据转换成List。(当然了Collectors类里面有提供这个方法,这里我们自己写一个也是为了方便大家的理解)

    // 自己来组装Collector,返回一个List
    @Test
    public void collectNew() {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        List<Integer> intList = stream.collect(
                new Collector<Integer, List<Integer>, List<Integer>>() {
                    // 生成结果容器,容器类型为,我们这里为List<Integer>
                    @Override
                    public Supplier<List<Integer>> supplier() {
                        return new Supplier<List<Integer>>() {

                            @Override
                            public List<Integer> get() {
                                return new ArrayList<>();
                            }
                        };
                    }

                    // 把流里面的结果都放到结果容器里面去
                    @Override
                    public BiConsumer<List<Integer>, Integer> accumulator() {
                        return new BiConsumer<List<Integer>, Integer>() {
                            @Override
                            public void accept(List<Integer> integers, Integer integer) {
                                integers.add(integer);
                            }
                        };
                    }

                    // 两个两个合并并行执行的线程的执行结果,将其合并为一个最终结果A
                    @Override
                    public BinaryOperator<List<Integer>> combiner() {
                        return new BinaryOperator<List<Integer>>() {
                            @Override
                            public List<Integer> apply(List<Integer> left, List<Integer> right) {
                                left.addAll(right);
                                return left;
                            }
                        };
                    }

                    // 可以对最终的结果做一个转换操作
                    @Override
                    public Function<List<Integer>, List<Integer>> finisher() {
                        return new Function<List<Integer>, List<Integer>>() {
                            @Override
                            public List<Integer> apply(List<Integer> integers) {
                                return integers;
                            }
                        };
                    }

                    // 特征值
                    @Override
                    public Set<Characteristics> characteristics() {
                        return EnumSet.of(Collector.Characteristics.UNORDERED, Collector.Characteristics.IDENTITY_FINISH);
                    }
                });

        for (Integer item : intList) {
            System.out.println(item);
        }
    }

3.1.2 Collectors

       Collectors是Collector的工具类,用来创建Collector对象。它内部已经给我们提供了各种各样的创建Collector对象的方法,已经能满足我们大部分的需求了,我们可以直接拿来使用,非常方便。

Collectors各个方法介绍

public final class Collectors {


    /**
     * 将流中的元素全部放置到一个集合中返回
     *
     * collectionFactory参数用于创建Collection对象(比如List,LinkeList等等)
     */
    public static <T, C extends Collection<T>>
    Collector<T, ?, C> toCollection(Supplier<C> collectionFactory);

    /**
     * 将流中的元素放置到一个列表集合中去。这个列表默认为ArrayList
     */
    public static <T>
    Collector<T, ?, List<T>> toList();

    /**
     * 将流中的元素放置到一个无序集set中去。默认为HashSet
     */
    public static <T>
    Collector<T, ?, Set<T>> toSet();

    /**
     * joining的目的是将流中的元素全部以字符序列的方式连接到一起,可以指定连接符,甚至是结果的前后缀
     */
    public static Collector<CharSequence, ?, String> joining();
    public static Collector<CharSequence, ?, String> joining(CharSequence delimiter);
    public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
                                                             CharSequence prefix,
                                                             CharSequence suffix);

    /**
     * 这个映射是首先对流中的每个元素进行映射,即类型转换,然后再将新元素以给定的Collector进行归纳
     */
    public static <T, U, A, R>
    Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,
                               Collector<? super U, A, R> downstream);

    /**
     * 该方法是在归纳动作结束之后,对归纳的结果进行再处理
     */
    public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream,
                                                                Function<R,RR> finisher);

    /**
     * 该方法用于计数
     */
    public static <T> Collector<T, ?, Long> counting();

    /**
     * 生成一个用于获取最小/最大值的Optional结果的Collector
     */
    public static <T> Collector<T, ?, Optional<T>> minBy(Comparator<? super T> comparator);
    public static <T> Collector<T, ?, Optional<T>> maxBy(Comparator<? super T> comparator);

    /**
     * 生成一个用于求元素和的Collector,首先通过给定的mapper将元素转换类型,然后再求和
     */
    public static <T> Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper);
    public static <T> Collector<T, ?, Long> summingLong(ToLongFunction<? super T> mapper);
    public static <T> Collector<T, ?, Double> summingDouble(ToDoubleFunction<? super T> mapper);

    /**
     * 生成一个用于求元素平均值的Collector,首选通过参数将元素转换为指定的类型
     */
    public static <T> Collector<T, ?, Double> averagingInt(ToIntFunction<? super T> mapper);
    public static <T> Collector<T, ?, Double> averagingLong(ToLongFunction<? super T> mapper);
    public static <T> Collector<T, ?, Double> averagingDouble(ToDoubleFunction<? super T> mapper);

    /**
     * 规约 对流中的元素做统计归纳作用
     * 和Stream类里面的reducing操作符一样
     */
    public static <T> Collector<T, ?, T> reducing(T identity, BinaryOperator<T> op);
    public static <T> Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op);
    public static <T, U> Collector<T, ?, U> reducing(U identity,
                                Function<? super T, ? extends U> mapper,
                                BinaryOperator<U> op);

    /**
     * 这个方法是用于生成一个拥有分组功能的Collector
     */
    public static <T, K> Collector<T, ?, Map<K, List<T>>>
    groupingBy(Function<? super T, ? extends K> classifier);
    public static <T, K, A, D>
    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                          Collector<? super T, A, D> downstream);
    public static <T, K, D, A, M extends Map<K, D>>
    Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
                                  Supplier<M> mapFactory,
                                  Collector<? super T, A, D> downstream);

    /**
     * 和groupingBy方法一样,只是返回的Collector是并行的
     */
    public static <T, K>
    Collector<T, ?, ConcurrentMap<K, List<T>>>
    groupingByConcurrent(Function<? super T, ? extends K> classifier);
    public static <T, K, A, D>
    Collector<T, ?, ConcurrentMap<K, D>> groupingByConcurrent(Function<? super T, ? extends K> classifier,
                                                              Collector<? super T, A, D> downstream);
    public static <T, K, A, D, M extends ConcurrentMap<K, D>>
    Collector<T, ?, M> groupingByConcurrent(Function<? super T, ? extends K> classifier,
                                            Supplier<M> mapFactory,
                                            Collector<? super T, A, D> downstream);

    /**
     * 该方法将流中的元素按照给定的校验规则的结果分为两个部分,放到一个map中返回,map的键是Boolean类型,值为元素的列表List。
     */
    public static <T>
    Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate);
    public static <T, D, A>
    Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate,
                                                    Collector<? super T, A, D> downstream);

    /**
     * 根据给定的键生成器和值生成器生成的键和值保存到一个map中返回,键和值的生成都依赖于元素,
     * 可以指定出现重复键时的处理方案和保存结果的map
     */
    public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper);
    public static <T, K, U>
    Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
                                    Function<? super T, ? extends U> valueMapper,
                                    BinaryOperator<U> mergeFunction);
    public static <T, K, U, M extends Map<K, U>>
    Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                             Function<? super T, ? extends U> valueMapper,
                             BinaryOperator<U> mergeFunction,
                             Supplier<M> mapSupplier);

    /**
     * 并发版本的toMap
     */
    public static <T, K, U>
    Collector<T, ?, ConcurrentMap<K,U>> toConcurrentMap(Function<? super T, ? extends K> keyMapper,
                                                        Function<? super T, ? extends U> valueMapper);

    public static <T, K, U>
    Collector<T, ?, ConcurrentMap<K,U>>
    toConcurrentMap(Function<? super T, ? extends K> keyMapper,
                    Function<? super T, ? extends U> valueMapper,
                    BinaryOperator<U> mergeFunction);

    public static <T, K, U, M extends ConcurrentMap<K, U>>
    Collector<T, ?, M> toConcurrentMap(Function<? super T, ? extends K> keyMapper,
                                       Function<? super T, ? extends U> valueMapper,
                                       BinaryOperator<U> mergeFunction,
                                       Supplier<M> mapSupplier);

    /**
     * 这三个方法适用于汇总的,返回值分别是IntSummaryStatistics,LongSummaryStatistics,DoubleSummaryStatistics
     */
    public static <T>
    Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper);
    
    public static <T>
    Collector<T, ?, LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper);
    
    public static <T>
    Collector<T, ?, DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper);
}

       为了加深大家的理解。接下来我们对Collectors里面的每个函数都写一个简单的实例。

3.1.2.1 toCollection

       将流中的元素全部放置到一个集合中返回,这里使用Collection,泛指多种集合。

    @Test
    public void toCollection() {
        List<String> list = Arrays.asList("java", "ios", "c");
        LinkedList<String> retList = list.stream().collect(Collectors.toCollection(
                new Supplier<LinkedList<String>>() {

                    @Override
                    public LinkedList<String> get() {
                        return new LinkedList<>();
                    }
                }));
    }

3.1.2.2 toList

       将流中的元素放置到一个列表集合中去。这个列表默认为ArrayList。

    @Test
    public void collectList() {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        List<Integer> intList = stream.collect(Collectors.toList());
    }

3.1.2.3 toSet

       将流中的元素放置到一个无序集set中去。默认为HashSet。

    @Test
    public void toSet() {
        List<String> list = Arrays.asList("java", "ios", "c");
        Set<String> retList = list.stream().collect(Collectors.toSet());
    }

3.1.2.4 joining

       joining的目的是将流中的元素全部以字符序列的方式连接到一起,可以指定连接符,甚至是结果的前后缀。

    @Test
    public void joining() {
        List<String> list = Arrays.asList("java", "ios", "c");
        String ret = list.stream().collect(Collectors.joining(":", "@@", "@@"));
        System.out.println(ret);//@@java:ios:c@@
    }

3.1.2.5 mapping

       这个映射是首先对流中的每个元素进行映射,即类型转换,然后再将新元素以给定的Collector进行归纳。

    @Test
    public void mapping() {
        List<String> list = Arrays.asList("java", "ios", "c");
        // 先把流里面的每个元素的前后加上[],之后在用:拼接起来
        String ret = list.stream().collect(Collectors.mapping(
                new Function<String, String>() {
                    @Override
                    public String apply(String s) {
                        return "[" + s + "]";
                    }
                },
                Collectors.joining(":")));
        System.out.println(ret);//[java]:[ios]:[c]
    }

3.1.2.6 collectingAndThen

       该方法是在归纳动作结束之后,对归纳的结果进行再处理。

    @Test
    public void collectingAndThen() {
        List<String> list = Arrays.asList("java", "ios", "c");
        // Collectors.toList()之后在把List<String>通过Joiner转换String
        String ret = list.stream().collect(Collectors.collectingAndThen(
                Collectors.toList(),
                new Function<List<String>, String>() {
                    @Override
                    public String apply(List<String> strings) {
                        return Joiner.on("; ")
                                .join(strings);
                    }
                }));
        System.out.println(ret);//java; ios; c
    }
    关于collectingAndThen有几个在编码过程经常用到的场景,根据list中对象的属性去重。
    @Test
    public void collectingAndThen() {
        List<Student> list = Arrays.asList(new Student("吴六", 26), new Student("张三", 26), new Student("李四", 27));
        List<Student> ret = list.stream().collect(
                Collectors.collectingAndThen(
                        Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Student::getAge))), ArrayList::new
                )
        );

        ret.forEach(new Consumer<Student>() {
            @Override
            public void accept(Student student) {
                System.out.println(student.toString());
            }
        });
    }
    
private static class Student {
        private String name;
        private int age;

        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }

3.1.2.7 counting

       计算流里面的元素个数。

    @Test
    public void counting() {
        List<String> list = Arrays.asList("java", "ios", "c");
        // 元素个数
        Long ret = list.stream().collect(Collectors.counting());
        System.out.println(ret);//3
    }

3.1.2.8 minBy/maxBy

       生成一个用于获取最小/最大值的Optional结果的Collector。

    @Test
    public void minBy() {
        List<String> list = Arrays.asList("java", "ios", "c");
        // 这里简单的用字符串比较的大小
        Optional<String> ret = list.stream().collect(Collectors.minBy(
                new Comparator<String>() {
                    @Override
                    public int compare(String o1, String o2) {
                        return o1.compareTo(o2);
                    }
                }));
    }

3.2.2.9 summingInt/summingLong/summingDouble

       生成一个用于求元素和的Collector,首先通过给定的mapper将元素转换类型,然后再求和。

    @Test
    public void summingInt() {
        List<Integer> list = Arrays.asList(1, 2, 3);
        // 求和
        Integer ret = list.stream().collect(Collectors.summingInt(
                new ToIntFunction<Integer>() {
                    @Override
                    public int applyAsInt(Integer value) {
                        return value;
                    }

                }));
    }

3.2.2.10 averagingInt/averagingLong/averagingDouble

       生成一个用于求元素平均值的Collector,首选通过参数将元素转换为指定的类型。

    @Test
    public void averagingInt() {
        List<Integer> list = Arrays.asList(1, 2, 3);
        // 求平均值
        Double ret = list.stream().collect(Collectors.averagingInt(
                new ToIntFunction<Integer>() {
                    @Override
                    public int applyAsInt(Integer value) {
                        return value;
                    }

                }));
    }

3.1.2.11 reducing

       reducing方法有三个重载方法,其实是和Stream里的三个reduce方法对应的,二者是可以替换使用的,作用完全一致,也是对流中的元素做统计归纳作用(规约)。

    @Test
    public void reducing() {
        List<Integer> list = Arrays.asList(1, 2, 3);
        // 求平均值
        Integer ret = list.stream().collect(Collectors.reducing(
                10,
                new Function<Integer, Integer>() {
                    @Override
                    public Integer apply(Integer integer) {
                        return integer * integer;
                    }
                },
                new BinaryOperator<Integer>() {
                    @Override
                    public Integer apply(Integer integer, Integer integer2) {
                        return integer + integer2;
                    }
                }));

        System.out.println(ret); // 10 + 1*1 + 2*2 + 3*3 = 24
    }

3.1.2.12 groupingBy

       这个方法是用于生成一个拥有分组功能的Collector,它也有三个重载方法。这里要稍微解释下参数最长的函数每个参数的含义。

  • Function<? super T, ? extends K> classifier:确定怎么从流里面每个元素获取key。
  • Supplier<M> mapFactory:用于创建最终要生成的对象一般都是Map。
  • Collector<? super T, A, D> downstream:每一组数据用什么来存放,一般是List,所以这个参数一般是Collectors.toList()。
    @Test
    public void collectGroupingBy() {
        List<Student> list = Arrays.asList(new Student("吴六", 26), new Student("张三", 26), new Student("李四", 27));
        Map<Integer, List<Student>> ret = list.stream().collect(Collectors.groupingBy(
                new Function<Student, Integer>() {
                    @Override
                    public Integer apply(Student student) {
                        return student.getAge();
                    }
                },
                new Supplier<Map<Integer, List<Student>>>() {
                    @Override
                    public Map<Integer, List<Student>> get() {
                        return new HashMap<>();
                    }
                },
                Collectors.toList()));

        for (Map.Entry<Integer, List<Student>> entry : ret.entrySet()) {
            Integer mapKey = entry.getKey();
            List<Student> mapValue = entry.getValue();
            System.out.println(mapKey + ":" + mapValue);
        }

    }


    private static class Student {
        private String name;
        private int age;

        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }

groupingByConcurrent和groupingBy是一样的,只不过groupingByConcurrent生成的是ConcurrentMap,而groupingBy生成的是Map。

3.1.2.13 partitioningBy

       该方法将流中的元素按照给定的校验规则的结果分为两个部分,放到一个map中返回,map的键是Boolean类型,值为元素的列表List。

    @Test
    public void partitioningBy() {
        List<Integer> list = Arrays.asList(1, 2, 3);
        // 求平均值
        Map<Boolean, List<Integer>> ret = list.stream()
                .collect(Collectors.partitioningBy(
                        new Predicate<Integer>() {
                            @Override
                            public boolean test(Integer integer) {
                                return integer % 2 == 0;
                            }
                        },
                        Collectors.toList()));

        for (Map.Entry<Boolean, List<Integer>> entry : ret.entrySet()) {
            Boolean mapKey = entry.getKey();
            List<Integer> mapValue = entry.getValue();
            System.out.println(mapKey + ":" + mapValue);
        }
    }

3.1.2.14 toMap

       toMap方法是根据给定的键生成器和值生成器生成的键和值保存到一个map中返回,键和值的生成都依赖于元素,可以指定出现重复键时的处理方案和保存结果的map。

    @Test
    public void toMap() {
        List<Student> list = Arrays.asList(new Student("吴六", 26), new Student("张三", 26), new Student("李四", 27));
        Map<Integer, List<Student>> ret = list.stream()
                .collect(Collectors.toMap(
                        // key
                        new Function<Student, Integer>() {
                            @Override
                            public Integer apply(Student student) {
                                return student.getAge();
                            }
                        },
                        // value
                        new Function<Student, List<Student>>() {


                            @Override
                            public List<Student> apply(Student student) {
                                return Lists.newArrayList(student);
                            }
                        },
                        // 有系统key的value怎么处理
                        new BinaryOperator<List<Student>>() {

                            @Override
                            public List<Student> apply(List<Student> students, List<Student> students2) {
                                students.addAll(students2);
                                return students;
                            }
                        }));

        for (Map.Entry<Integer, List<Student>> entry : ret.entrySet()) {
            Integer mapKey = entry.getKey();
            List<Student> mapValue = entry.getValue();
            System.out.println(mapKey + ":" + mapValue);
        }

    }


    private static class Student {
        private String name;
        private int age;

        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }

3.1.2.15 summarizingInt/summarizingLong/summarizingDouble

       这三个方法适用于汇总的,返回值分别是IntSummaryStatistics,LongSummaryStatistics,DoubleSummaryStatistics。

    @Test
    public void summarizingInt() {
        List<Integer> list = Arrays.asList(1, 2, 3);
        IntSummaryStatistics ret = list.stream()
                .collect(Collectors.summarizingInt(
                        new ToIntFunction<Integer>(){
                            @Override
                            public int applyAsInt(Integer value) {
                                return value;
                            }
                        }));
        System.out.println(ret.getAverage());
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,653评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,321评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,833评论 0 324
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,472评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,306评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,274评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,658评论 3 385
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,335评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,638评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,697评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,454评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,311评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,699评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,986评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,254评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,647评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,847评论 2 335

推荐阅读更多精彩内容