【Java】Stream

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010900754/article/details/82728943

stream是java8新特性之一,主要提供了对集合和数组的流式处理api,而且还提供了并行操作。

使用stream的api处理数据通常会包含三个过程:获取source;intermediate操作;terminate操作;

1.获取source:

这一步是把集合或者数组对象转换成stream对象的过程,stream对象是一个管道,后面的操作都是对stream做处理的。steam并不等同于原先的集合对象。

创建stream对象:

    @Test
    public void test(){
        //1. 直接创建流;
        Stream<String> stream1 = Stream.of("liyao", "liuzhenyin");

        //2. 通过数组创建流;
        String[] strArray = new String[]{"liyao", "liuzhenyin"};
        Stream<String> stream2 = Arrays.stream(strArray);

        //3. 通过集合创建流;
        List<String> strList = new ArrayList<>();
        Stream<String> stream3 = strList.stream();

        Set<String> strSet = new HashSet<>();
        Stream<String> stream4 = strSet.stream();
    }

这便是我们常用的三种创建stream的方式,第二种数组和第三种集合是最常用的。数组需要使用Arrays的静态方法,集合则在Collections接口中定义了获取流的方法。

另外对于基本类型的集合,比如int,double,long,java8提供了三种对应的基本类型的流,intStream,doubleStream和longStream,可以减去使用stream时的自动装箱和拆箱的代价。

接着看一下基于stream的操作分类

  • Intermediate:

map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

  • Terminal:

forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

intermediate操作可以有多个,但是terminal操作只能有一个。intermediate操作如果有多个,并不是每一个操作都需要迭代一次,而是会把这些操作组成一个操作组,然后在一次迭代里面全部完成。

2.intremediate操作:

2.1 map:可以将流里的每一个元素将换成另外一个。一对一的映射。

    @Test
    public void test3(){
        List<String> list = new ArrayList<>();
        list.add("liyao");
        list.add("liuzhenyin");

        System.out.println(list.stream().map(e->"hello " + e).collect(Collectors.toSet()));
    }

输出:[hello liyao, hello liuzhenyin]

2.2 flatMap:与map函数的区别在于这里是一对多的映射,扁平化处理。

什么意思?看例子。

    public void test4(){
        List<Integer[]> list = new ArrayList<>();
        list.add(new Integer[]{1,2,3});
        list.add(new Integer[]{10,11,12});
        list.add(new Integer[]{99,100});

        System.out.println(list.stream().flatMap(Arrays::stream).collect(Collectors.toSet()));
    }

输出:[1, 2, 3, 99, 100, 10, 11, 12]

这里有一个存了int数组的list,然后通过stream的flatMap函数将list里的数组的元素全部输出。那么一对多就体现在每一个要处理的元素最终会被映射为一个stream,一个stream就会对应多个元素,最终flapMap函数会把每一个stream里的元素整合在一起。

再看一个例子,将文本文件里的每一行的单词提取出来。

    @Test
    public void test5(){
        List<String> list = new ArrayList<>();
        list.add("hello world");
        list.add("hello java");
        list.add("hello c++");

        System.out.println(list.stream().flatMap(e->Arrays.stream(e.split(" "))).distinct().collect(Collectors.toSet()));
    }

输出:[c++, world, java, hello]

以下是intStream的flatMap源码:

 @Override
    public final IntStream flatMap(IntFunction<? extends IntStream> mapper) {
        return new StatelessOp<Integer>(this, StreamShape.INT_VALUE,
                                        StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT | StreamOpFlag.NOT_SIZED) {
            @Override
            Sink<Integer> opWrapSink(int flags, Sink<Integer> sink) {
                return new Sink.ChainedInt<Integer>(sink) {
                    @Override
                    public void begin(long size) {
                        downstream.begin(-1);
                    }

                    @Override
                    public void accept(int t) {
                        try (IntStream result = mapper.apply(t)) {
                            // We can do better that this too; optimize for depth=0 case and just grab spliterator and forEach it
                            if (result != null)
                                result.sequential().forEach(i -> downstream.accept(i));
                        }
                    }
                };
            }
        };
    }

可以看到最后使用mapper函数做了变换,然后将得到的每一个stream都foreach加入到最终的stream中。

2.3 filter:

接受一个predictate接口的实例,用于过滤元素,只留下返回true的元素。

2.4 foreach:

接受一个consumer接口的实例,用于对流里的每一个元素做处理。

2.5 reduce:

规约操作,用于对流里的元素做统计,也就是一个多对一的过程。原理上和sum,max,min是一个含义。

    @Test
    public void test6(){
        IntStream stream = Arrays.stream(new int[]{1,2,3});
        System.out.println(stream.reduce(0, Integer::sum));
    }

结果:6

这里reduce的签名为:

    T reduce(T identity, BinaryOperator<T> accumulator);

第一个参数是初始值,第二个参数是一个operator,可以理解为如何计算流里的两个元素,会依次迭代式调用这个operator。当然,reduce还有另外两个重载版本,这里就不介绍了。

2.6 sort/limit/skip 这些从名字上就明白,不介绍了。

2.7 anyMatch/allMatch/nonMatch 

3 terminal操作:

collect函数负责将流里的元素转换成最终的集合或者数组类型。

之前例子里已经有了对toSet和toList的使用。

下面看一下groupingby和partitioningby函数。

3.1 groupingby:

    @Test
    public void test7(){
        List<Person> list = new ArrayList<>();
        list.add(new Person(24, "liyao"));
        list.add(new Person(24, "lizhenyin"));
        list.add(new Person(19, "liboyang"));

        Map<Integer, List<Person>> map1 = list.stream().collect(Collectors.groupingBy(Person::getAge));
        System.out.println(map1);

        Map<Integer, Long> map2 = list.stream().collect(Collectors.groupingBy(Person::getAge, Collectors.counting()));
        System.out.println(map2);

        Map<Integer, Integer> map3 = list.stream().collect(Collectors.groupingBy(Person::getAge, Collectors.summingInt(e->e.name.length())));
        System.out.println(map3);
    }

输出:

{19=[com.liyao.AppTest$Person@e2d56bf],

24=[com.liyao.AppTest$Person@244038d0,com.liyao.AppTest$Person@5680a178]}
{19=1, 24=2}
{19=8, 24=14}

如果只提供一个参数,那么会按照提供的分类函数把流里的数据分组,每一个key对应一个list。比如第一个例子。

当然,还可以对分组后的每组进行统计。比如统计个数,或者求和。比如第二个和第三个例子。

第二个计算每组的个数,第三个返回每组的名字的长度之和。第二个参数collectors里面提供了若干实现,可以查看。

再看一个:

public void test8(){
        System.out.println(Arrays.asList(1,2,3,4,4,4).stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting())));
    }

输出:

{1=1, 2=1, 3=1, 4=3}
indentity函数直接返回元素本身。

3.1 partitioningby:

与group类似,只不过接受的第一个参数是一个preindicate类型的实例,所以最终只能分成两组,一个true,一个false。

    public void test9(){
        System.out.println(Arrays.asList(1,2,3,4,4,4).stream().collect(Collectors.partitioningBy(e->e>3)));
    }

输出:

{false=[1, 2, 3], true=[4, 4, 4]}

猜你喜欢

转载自blog.csdn.net/u010900754/article/details/82728943