Java 8 Stream理解及实践

前言

Java8 中新增的重要特性 Stream,我个人认为是非常方便的一套数据处理API。接下来,本人会以Stream的产生,处理,最终汇聚产出三大部分简单阐述这套API的魅力。希望对各位有所帮助

我对Java Stream的理解

Java的 Stream, 就像是流水线中货物,从一开始的原材料(Stream的生成),到中间流水线工人的加工(map,flatMap,filter),然后到最后的包装出品(reduce和collect)。经历每个步骤就能让从原材料到我们想看到的结果。

流水线只会往一个方向流,材料也加工了之后也没办法复原,所以Stream有一个很大的特性就是不可复用。同时还有 惰性执行并行执行

代码实践

本部分会分为 Stream 生成处理终端输出操作三部分来做代码练习。

Stream生成

  1. 集合类 Collection 直接调用 stream() 生成流对象。
static List<Students> students = List.of(
            new Students(0,"A",91,10),
            new Students(1,"B",11,30),
            new Students(0,"B",10,50),
            new Students(1,"C",12,70),
            new Students(1,"C",10.2,12),
            new Students(0,"A",0.5,28),
            new Students(0,"B",99,21),
            new Students(0,"B",99,21)
    );

        // create Stream
        Double average = students.stream().map(m -> m.getChi()).collect(Collectors.averagingDouble(Double::doubleValue));
        System.out.println("1.average is " + average);

        //find first
        System.out.println("2. findFirst");
        students.stream().findFirst().ifPresent(System.out::println);

        //count ==  size
        System.out.println("3. count()");
        System.out.println(students.stream().count());//size()

        //distinct()
        System.out.println("4. distinct:");
        students.stream().distinct().forEach(System.out::println); // dependence on equal()

        System.out.println("5. limit()");
        students.stream().limit(2).forEach(System.out::println);

输出:

Students{gender=0, classNum='A', chi=91.0, maths=10.0}
Students{gender=1, classNum='B', chi=11.0, maths=30.0}
Students{gender=0, classNum='B', chi=10.0, maths=50.0}
Students{gender=1, classNum='C', chi=12.0, maths=70.0}
Students{gender=1, classNum='C', chi=10.2, maths=12.0}
Students{gender=0, classNum='A', chi=0.5, maths=28.0}
Students{gender=0, classNum='B', chi=99.0, maths=21.0}
Students{gender=0, classNum='B', chi=99.0, maths=21.0}
1.average is 41.5875
2. findFirst
Students{gender=0, classNum='A', chi=91.0, maths=10.0}
3. count()
8
4. distinct:
Students{gender=0, classNum='A', chi=91.0, maths=10.0}
Students{gender=1, classNum='B', chi=11.0, maths=30.0}
Students{gender=0, classNum='B', chi=10.0, maths=50.0}
Students{gender=1, classNum='C', chi=12.0, maths=70.0}
Students{gender=1, classNum='C', chi=10.2, maths=12.0}
Students{gender=0, classNum='A', chi=0.5, maths=28.0}
Students{gender=0, classNum='B', chi=99.0, maths=21.0}
5. limit()
Students{gender=0, classNum='A', chi=91.0, maths=10.0}
Students{gender=1, classNum='B', chi=11.0, maths=30.0}
  1. 还有一下这些方式
//Stream 生成
        System.out.println("======Stream 生成 S======");
        Stream<String> stream = Stream.of("a", "b", "c");
        stream.forEach(System.out::println);

        Stream<String> stream1 = Stream.generate(() -> "a").limit(3);
        stream1.forEach(System.out::println);

        IntStream stream2 = "Hello,lalala".chars();
        stream2.forEach(System.out::println);

        Stream<String> stream3 = Pattern.compile("Hello").splitAsStream("KKHelloLL");
        stream3.forEach(System.out::println);

        Stream<Integer> streamIterated = Stream.iterate(1, n -> n + 2).limit(3);
        streamIterated.forEach(System.out::println);
        System.out.println("======Stream 生成 E======");

output:

======Stream 生成 S======
a
b
c
a
a
a
72
101
108
108
111
44
108
97
108
97
108
97
KK
LL
1
3
5
======Stream 生成 E======

虽然有很多中方式生成Stream对象,但生产上,我们主要还是面对集合类作操作。

Stream处理

以下为Stream中我经常会用到的操作。除此之外,在jdk8之后 还有其他API,如:takeWhile(),dropWhile(),mapMulti() …

        //operation
        Stream<String> stream = Stream.of("a", "b", "c");
        // 每个字符串后面加上 mapped
        stream.map(s -> s + ": mapped.").forEach(System.out::println);
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
        //每个都乘以2
        integerStream.map(s -> s *= 2).forEach(System.out::println);

        stream = Stream.of("a", "b", "c");
        //flatMap就是
        stream.flatMap(s -> Stream.of(s + ": mapped.")).forEach(System.out::println);
        //filter
        stream = Stream.of("a", "b", "ac");
        stream.filter(s -> s.contains("a")).forEach(System.out::println);
        //concat
        Stream<String> stringStream1 = Stream.of("c", "o", "n");
        Stream<String> stringStream2 = Stream.of("c", "a", "t");
        Stream.concat(stringStream1, stringStream2).forEach(System.out::print);

终端操作

Stream类自带的一些终端操作方法

Stream 类自带一些常用的终端操作方法,如count,max,toList, toArray, anyMatch,

        // reduction 1. 预设的方法, 2. 自己基于 reduce 与 collect 来自定义的产出函数。
        System.out.println("--- 预设的方法 ----");
        System.out.println(strings.stream().count());
        System.out.println(strings.stream().max((a, b) -> (int) a.charAt(1) - (int) b.charAt(1)).get());
        IntSummaryStatistics intSummaryStatistics = strings.stream().mapToInt(s -> s.charAt(1)).summaryStatistics();
        System.out.println(intSummaryStatistics);

        //数据处理 Stream 的数据处理能力非常强大
        System.out.printf("男生人数:%s \n", students.stream().filter(s -> s.getGender() == 0).count());
        System.out.printf("总分最高:%s \n", students.stream().mapToDouble(s -> s.getChi() + s.getMaths()).max().getAsDouble());

output:

--- 预设的方法 ----
6
f6
IntSummaryStatistics{count=6, sum=309, min=49, average=51.500000, max=54}
男生人数:5 
总分最高:120.0 

reduce

如果jdk的操作方法不符合你的需求,你还可以使用reduce来处理。只需要传入一个累加器作为参数

//reduce 实现找出数学最高分的学生
students.stream().reduce((a,b) -> a.getMaths() > b.getMaths() ? a : b).ifPresent(System.out::println);

简单理解累加器就是,两个入参一个return

collect

如果简单的reduce无法满足你的需求则需要考虑自己编写 collector,

你先需要用Collector.of编写一个collector,三个参数分别是:

  1. 被输出新容器的提供者(supplier),这里需要return的是Map,则是Map的new方法,或者你自己写一个函数也可以
  2. 累加器(accumulator),这个累加器输出的是新的容器Map
  3. 合成器(combiner),通过前面两个步骤出来了一堆的 Map,但最终结果只会是一个方法他们合并在一起。

以下这个方法,其实就是按照班分类,算出不同班的数学总分。

        Collector<Students,? , Map<String,Double>> collector = Collector.of(HashMap::new, (a, b) -> {
            a.put(b.getClassNum(), b.getMaths());
        },(a,b) ->{
           b.forEach((key, value) -> a.put(key, a.getOrDefault(key, 0.0) + value));
           return a;
        });
        Map<String, Double> collect = students.parallelStream().collect(collector);
        collect.forEach((k,v) -> System.out.println(k + " " + v));

output

-------- Collector ------------
A 38.0
B 122.0
C 82.0

JDK同样提供很多collector给予我们使用,接下来的代码则是利用了groupingBy 达到同样的效果,且代码非常简洁。

Map<String, DoubleSummaryStatistics> collect1 = students.parallelStream().collect(Collectors.groupingBy(Students::getClassNum, Collectors.summarizingDouble(Students::getMaths)));
        collect1.forEach((k,v) -> System.out.println(k + " " + v.getSum()));

特性

分别用以下几段代码简单体现出他这些特性。

  1. 不可复用
        Stream<Integer> oneFive = Stream.iterate(1, i -> i + 1).limit(5);
        oneFive.forEach(System.out::println);
        oneFive.forEach(System.out::print);

则会报错

Exception in thread “main” java.lang.IllegalStateException: stream has already been operated upon or closed

  1. 惰性执行
//懒加载

        List<String> strings = Arrays.asList("a1", "b2", "c3", "d4", "e5", "f6");
        //这里只会输出一次。这属于 Stream的懒加载 懒加载取决于最后聚合哪一环。
        Optional<String> first = strings.stream().map(s -> {
            System.out.printf("current mapping is : %s ", s);
            return s;
        }).findFirst();
  1. 并行

并行则是调用.parallelStream() 方法实现。

结语

Java中的stream 是一套非常强大的数据处理 API, 通过简单的演示可以看出这套API 有多种处理方式,现在普遍公司面试时都会考这方面的知识。

本文有抛砖引玉之意,望读者 若有更多更好的理解 非常欢迎评论。

参考
[1]: https://www.baeldung.com/java-8-streams

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值