用了多年Java,你的团队会使用Java8 Stream API吗?

Lambda

在介绍Stream之前,我们先来说说Java8中的另一个新特性:Lambda,直接上代码吧

在Java8之前,list自定义排序会使用匿名内部类,实现自己的排序逻辑

        stuList.sort(new Comparator<Student>() {
            @Override
            public int compare(Student stu1, Student stu2) {
                return stu1.getAge().compareTo(stu2.getAge());
            }
        });

下面看一下Lambda表达式的写法:

stuList.sort((s1,s2) -> s1.getAge().compareTo(s2.getAge()));

可以看到,从代码量和整洁程度来说,Lambda都显得都简洁

什么是Lambda

Lambda表达式是表示可传递匿名方法的一种简洁方式,Lambda表达式没有名称,但是有参数列表、函数主体、返回类型,还可能有一个可以抛出的异常列表

Lambda的组成

|<----------参数--------->|<-箭头->|<----------------主体---------------|
(Student stu1,Student stu2)   ->   stu1.getAge().compareTo(stu2.getAge())

  • 参数列表:上例中的两个参数是传给匿名方法的
  • 箭头:连接参数和主体,是操作符
  • 主体:是方法的逻辑处理部分,它可以接收参数列表中的参数

从上面Lambda的组成可以看出来,Lambda有两种简单的语法:

  • 参数列表 -> 表达式
  • 参数列表 -> {多个表达式组成的语句}

方法引用

方法引用是Java8中引入的新特性,它提供了一种引用方法而不执行方法的方式,可以让我们重复使用现用方法的定义,做为某些Lambda表达式的另一种更简洁的写法

比如:

stuList.sort((s1,s2) -> s1.getAge().compareTo(s2.getAge()));

可以使用方法引用简写为:

Comparator comparator = Comparator.comparing(Student::getAge);

当你需要方法引用时,目标引用放在分隔符::前,方法的名称放在分隔符::后。比如,上面的Student::getAge,就是引用了Student中定义的getAge方法。方法名称后不需要加括号,因为我们并没有实际调用它。方法引用提高了代码的可读性,也使逻辑更加清晰

好了,Lambda就简单的介绍到这里,更多Lambda的详细用法可以直接百度自己学习一下

Stream

Java8中添加一个新的抽象概念叫做Stream,它可以使用类似SQL语句的形式来处理Java集合的运算。

Stream提供了一种“链式”编程,能把一系列的操作像链条一样串连起来,这种风格的处理方式把集合看作是一种流,流在管道中传输,每一步操作都可以看作是管道中的一个节点,所有节点处理完成形成最终的结果

举个形象的例子,Stream的操作类似于流水线生产,整个产品线从头到尾形成一条链路,每个环节就是一个处理节点,最终所有节点处理完成就形成了最终的产品

Stream API简化了集合的各种操作,提高了程序员的生产力,让程序员能写出高效率、干净、整洁的代码

流的常见创建方式

    public void create() throws FileNotFoundException {
        Stream<String> stringStream = Stream.of("a","b","c");
        Stream<String> stringStream1 = Stream.of("d","e","f");
        //通过concat将两个Stream连接成一个Stream
        Stream<String> stringStream2 = Stream.concat(stringStream,stringStream1);
        stringStream2.forEach(str -> System.out.println(str));
        //通过迭代器创建Stream
        Stream<Integer> integerStream = Stream.iterate(0,(x) -> (x + 1)).limit(10);
        //generate创建Stream
        Stream<Double> doubleStream =  Stream.generate(Math::random).limit(5);

        //通过集合创建stream
        List<String> list = Lists.newArrayList("a","b","c");
        Stream<String> listStream = list.stream();
        Stream<String> parallelStream = list.parallelStream();//并发流

        //数组转换成Stream
        String[] stringArr = new String[]{"a","b","c"};
        Stream<String> arrStream = Arrays.stream(stringArr);

        //文件流转换成Stream
        BufferedReader reader = new BufferedReader(new FileReader("D:\\test.txt"));
        Stream<String> fileStream = reader.lines();

    }

Stream中的操作分为两类

  • 中间操作
  • 终止操作
|<------Stream------>|       |<-------中间操作--------->|<--终止操作-->|
+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+       +------+   +------+   +---+   +-------+

中间操作

中间操作就是操作之后,Stream仍然是一个Stream,可以使用其他的中间操作继续对结果进行操作

  • map 传入一个函数,将list中的所有元素都套用这个函数
  • flatMap 传入一个函数,将每个元素转换成另外一个流,再将所有的流连接成一个流
  • filter 传入过滤的规则,对stream中的元素进行过滤
  • limit 传入一个数字,取stream中前面几个元素
  • skip 传入一个数字,跳过几个元素
  • distinct 去掉重复元素
  • sorted 对元素进行自然排序,当遇到对象排序时,可以自定义通过对象的属性来排序
  • peek 传入一个Consumer,没有返回值,不改变Stream中原来的元素,注意对比map
map、flatMap用法
    public void mapTest(){
        List<Integer> list = Lists.newArrayList(1,2,3,4,5);
        //map就是传入一个函数,将list中的所有元素都套用这个函数
        Stream<Integer> stream = list.stream().map(x -> x * x);
        stream.forEach(x -> System.out.println(x));

        List<String> flapList = Lists.newArrayList("a#b#c","1#2#3");
        //flatMap就是传入一个函数,将每个元素转换成另外一个流,再将所有的流连接成一个流
        Stream<String> flagStream = flapList.stream().flatMap(x -> {
           String[] strArr = x.split("#");
           Stream<String> tmpStream = Arrays.stream(strArr);
           return tmpStream;
        });
        flagStream.forEach(x -> System.out.println(x)); //a b c 1 2 3
    }
filter用法
    public void filterTest(){
        List<Integer> list = Lists.newArrayList(1,2,3,4,5);
        //filter就是传入过滤的规则,对stream中的元素进行过滤
        list.stream().filter(x -> x > 3).forEach(x -> System.out.println(x));
    }
limit用法
    public void limitTest(){
        List<String> list = Lists.newArrayList("a","b","c","x","y","z");
        //limit传入一个数字,取stream中前面几个元素
        list.stream().limit(3).forEach(x -> System.out.println(x));
    }
skip用法
    public void skipTest(){
        List<Integer> list = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15);
        //跳过前三个元素
        list.stream().skip(3);
        //skip配合limit可实现分页,比如每页5笔记录
        list.stream().skip(5 * 0).limit(5);//第一页
        list.stream().skip(5 * 1).limit(5);//第二页
        list.stream().skip(5 * 2).limit(5);//第三页
    }
distinct用法
    public void distinctTest(){
        List<String> list = Lists.newArrayList("a","b","c","a","a","c","c","e","f");
        //去掉重复元素
        list.stream().distinct();
    }
sorted用法
    public void sortedTest(){
        //sorted是对元素进行自然排序,当遇到对象排序时,可以自定义通过对象的属性来排序
        List<String> list = Lists.newArrayList("a","b","c","a","a","c","c","e","f");
        list.stream().sorted();

        List<Integer> intList = Lists.newArrayList(1,5,5,3,9,4,4,7,6);
        intList.stream().sorted();
    }
peek用法
    public void peekTest(){
        List<String> list = Lists.newArrayList("a","b","c","a","a","c","c","e","f");
        //peek传入一个Consumer,没有返回值,不改变Stream中原来的元素,注意对比map
        list.stream().peek(x -> x = x + "666");
    }

终止操作

终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次

  • matchAll 接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false
  • noneMatch 接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false
  • anyMatch 接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false
  • findFirst 返回流中第一个元素
  • findAny 返回流中的任意元素
  • collect 收集操作
  • count 统计操作
  • min、max 最值操作
  • reduce 规约操作。所有的元素规约成一个,比如对所有元素求和,求积等
  • forEach、forEachOrdered 遍历操作
  • toArray 数组操作
matchAll、noneMatch、anyMatch用法
    public void matchTest(){
        List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
        list.stream().allMatch(x -> x > 10); //所有元素满足条件才返回true
        list.stream().noneMatch( x -> x > 100);//所有元素不满足条件才返回true
        list.stream().anyMatch(x -> x > 50);//只有一个元素满足就返回true
    }
findFirst、findAny用法
    public void findTest(){
        List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
        list.stream().findFirst().get();//返回Stream中第一个元素
        list.stream().findAny().get();//返回Steam中任意元素
    }
collect用法

将序列的内容重新组织到新的容器中,比如 List 或者 Map

    public void collectTest(){
        Student stu1 = new Student("stu1",11);
        Student stu2 = new Student("stu2",12);
        Student stu3 = new Student("stu3",13);
        Student stu4 = new Student("stu4",14);
        Student stu5 = new Student("stu5",15);
        List<Student> list = Lists.newArrayList(stu1,stu2,stu3,stu4,stu5);
        //将list中所有学生的姓名收集起来,放到一个新list中
        List<String> nameList = list.stream().map(Student::getName).collect(Collectors.toList());
        nameList.forEach(System.out::println);
    }

Collectors

Collectors提供了很多种Collector的创建方式

    public void CollectorsTest(){
        Student stu1 = new Student("stu1",11);
        Student stu2 = new Student("stu2",12);
        Student stu3 = new Student("stu3",13);
        Student stu4 = new Student("stu4",14);
        Student stu5 = new Student("stu5",15);
        List<Student> list = Lists.newArrayList(stu1,stu2,stu3,stu4,stu5);
        //将所有学生的姓名收集到新的list中
        List<String> nameList = list.stream().map(Student::getName).collect(Collectors.toList());
        //同样可以收集到Set中
        Set<String> nameSet = list.stream().map(Student::getName).collect(Collectors.toSet());
        //将每个学生的name,age转换成Map,若有名称相同的会造成key相同导致失败
        Map<String,Integer> map = list.stream().collect(Collectors.toMap(Student::getName,Student::getAge));
        //字符串连接
        String nameJoin = list.stream().map(Student::getName).collect(Collectors.joining(";","(",")"));

        //---聚合操作----
        //count
        Long count = list.stream().collect(Collectors.counting());
        // max
        Integer maxAge = list.stream().map(Student::getAge).collect(Collectors.maxBy(Integer::compare)).get();
        // sum
        Integer sumAge = list.stream().collect(Collectors.summingInt(Student::getAge));
        // avg
        Double avgAge = list.stream().collect(Collectors.averagingDouble(Student::getAge));
        // 所有聚合操作的返回
        DoubleSummaryStatistics summaryStatistics = list.stream().collect(Collectors.summarizingDouble(Student::getAge));
        summaryStatistics.getSum();
        summaryStatistics.getAverage();
        summaryStatistics.getCount();
        summaryStatistics.getMax();
        summaryStatistics.getMin();

        //group 按年龄分组
         Map<Integer,List<Student>> ageGroup = list.stream().collect(Collectors.groupingBy(Student::getAge));

         //分区,分成两部分,一部分大于12岁,一部分小于等于12岁
        Map<Boolean,List<Student>> agePartition = list.stream().collect(Collectors.partitioningBy(x -> x.getAge() > 12));

        //规约
        Integer reducingAge = list.stream().map(Student::getAge).collect(Collectors.reducing(Integer::max)).get();

    }

可以看到Collectors提供了很多方便的收集方式,让我们很方便的处理集合的各种操作

count用法
    public void countTest(){
        List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
        Long count = list.stream().count();
    }
min、max用法
    public void maxAndminTest(){
        List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
        Integer max = list.stream().max(Integer::compareTo).get();
        Integer min = list.stream().min(Integer::compareTo).get();
    }
reduce用法
    public void reduceTest(){
        List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
        Integer sum = list.stream().reduce(0,(x,y) -> x + y);
    }
forEachOrdered用法

适用用于并行流的情况下进行迭代,能保证迭代的有序性

    public void forEachOrderedTest(){
        List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
        list.stream().parallel().forEachOrdered(System.out::println);
    }
toArray用法
    public void toArrayTest(){
        List<Integer> list = Lists.newArrayList(12,14,3,24,5,23,77);
        Object[] objects = list.stream().toArray();
        for(Object obj : objects){
            System.out.println(obj);
        }
    }

总结

  • Lambda表达式是表示可传递匿名方法的一种简洁方式
  • 从上面Lambda的组成可以看出来,Lambda有两种简单的语法:
    • 参数列表 -> 表达式
    • 参数列表 -> {多个表达式组成的语句}
  • 方法引用是Java8中引入的新特性,它提供了一种引用方法而不执行方法的方式,可以让我们重复使用现用方法的定义,做为某些Lambda表达式的另一种更简洁的写法
  • Java8中添加一个新的抽象概念叫做Stream,它可以使用类似SQL语句的形式来处理Java集合的运算
  • Stream API简化了集合的各种操作,提高了程序员的生产力,让程序员能写出高效率、干净、整洁的代码
  • Stream中的操作分为两类
    • 中间操作:中间操作就是操作之后,Stream仍然是一个Stream,可以使用其他的中间操作继续对结果进行操作
      • map 传入一个函数,将list中的所有元素都套用这个函数
      • flatMap 传入一个函数,将每个元素转换成另外一个流,再将所有的流连接成一个流
      • filter 传入过滤的规则,对stream中的元素进行过滤
      • limit 传入一个数字,取stream中前面几个元素
      • skip 传入一个数字,跳过几个元素
      • distinct 去掉重复元素
      • sorted 对元素进行自然排序,当遇到对象排序时,可以自定义通过对象的属性来排序
      • peek 传入一个Consumer,没有返回值,不改变Stream中原来的元素,注意对比map
    • 终止操作:终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次
      • matchAll 接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false
      • noneMatch 接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false
      • anyMatch 接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false
      • findFirst 返回流中第一个元素
      • findAny 返回流中的任意元素
      • collect 收集操作
      • count 统计操作
      • min、max 最值操作
      • reduce 规约操作。所有的元素规约成一个,比如对所有元素求和,求积等
      • forEach、forEachOrdered 遍历操作
      • toArray 数组操作

通过这些方法的介绍,相信你对Java8 Stream API有了一定的了解,熟练掌握这些方法能使你的代码更加简洁。对于Java8这种“链式”编程的风格到底是否应该推荐使用也是见仁见智,网上很多人说Stream API如果写的很长,确实可以减少代码,但这样会导致逻辑不够清晰,写的人很爽,看的人难受。笔者所在技术团队目前还没有大量使用Stream API,大家还是习惯于用平常的语法来写业务逻辑。

你所在的团队会大量的用到Stream API来减少代码量吗?欢迎给我留言~

如果感觉对你有些帮忙,请收藏好,你的关注和点赞是对我最大的鼓励!
如果想跟我一起学习,坚信技术改变世界,请微信搜索【Java天堂】关注我,我会定期分享自己的学习成果,第一时间推送给您

参考:

https://blog.csdn.net/y_k_y/article/details/84633001

https://www.jianshu.com/p/11c925cdba50

猜你喜欢

转载自blog.csdn.net/pzjtian/article/details/107010328