美文网首页
JDK新特性(五)——Stream流

JDK新特性(五)——Stream流

作者: moutory | 来源:发表于2021-03-30 14:53 被阅读0次

    前言

    Stream流的概念是在JDK1.8的时候提出来的,是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。通常我们需要多行代码才能完成的操作,借助于Stream流式处理可以很简单的实现。本文将对Stream流的工作流程和常用的api进行讲解,希望对各位读者有所帮助。

    提示:如果你对Lambda表达式、函数式接口和方法引用等概念没有了解,建议先去阅读本系列文章中这部分的内容,再来阅读本文:
    JDK新特性(一)——Lambda表达式
    JDK新特性(三)——方法引用
    JDK新特性(四)——函数式接口


    一、Stream流的初使用

    我们在前言中讲到,Stream流可以实现对集合对象处理步骤的简化和大批量数据的操作,相比于本系列其他文章而言,Stream流可能是我们工作中相对来说使用和接触得更多的特性了。
    下面我们不妨来看一个例子,体验一下Stream流的强大之处:

    要求我们定义一个方法,把字符串数组中以张开头,字符串长度大于2的字符进行输出。
    public class StreamTest {
    
        public static void main(String[] args) {
            String[] arr = {"张三丰","张超","章口就来","张口就来"};
            filterStrMethod(arr);
        }
    
        private static void filterStrMethod(String[] arr){
            List<String> nameList = new ArrayList<>();
            List<String> lengthList = new ArrayList<>();
            for (int i = 0; i < arr.length; i++) {
                if(arr[i].startsWith("张")){
                    nameList.add(arr[i]);
                }
            }
            for (int i = 0; i < nameList.size(); i++) {
                if(nameList.get(i).length()>2){
                    lengthList.add(nameList.get(i));
                }
            }
            System.out.println(lengthList);
        }
    }
    

    (上面这个方法自然可以只用一个for循环解决,目的主要是出于模拟过滤两次的效果)
    我们可以看到,使用常规的for循环虽然可以解决问题,但相对来说代码量不少。我们再来看看使用Stream流可以怎么样来优化解决的方案:

    public class StreamTest {
    
        public static void main(String[] args) {
            String[] arr = {"张三丰","张超","章口就来","张口就来"};
            filterStrByStream(arr);
        }
    
        private static void filterStrByStream(String[] arr){
            Stream.of(arr).filter(s -> s.startsWith("张")).filter(s -> s.length()>2).forEach(System.out::println);
        }
    
    }
    

    我们可以看到,使用Stram流的方式后,原先的需求我们只用了一行代码就实现了!!!
    这就是Stream流的强大之处,让我们的代码更简洁,更简单直接。Stream流把真正的函数式编程风格引入到Java中

    二、Stream流的生成方式

    我们对流的操作一般来说会从三个阶段进行,分别是流的生成、流的中间操作和流的终结操作方法。我们将源数据转换成流对象后,可以对其进行过滤、排序、元素跳跃和元素限制等操作,最终再使用例如foreach的方法对流进行结束。

    流的工作流程图

    三、Stream流操作篇

    (一)Stream流的生成方式

    Stream流支持多种渠道的数据源转为流对象,下面将主要进行Collection等集合类的流生成方式

    Collection体系集合
    使用默认方法stream()生成流, default Stream stream()
    
    Map体系集合
    把Map转成Set集合,间接的生成流
    
    数组
    通过Stream接口的静态方法of(T... values)生成流
    

    下面我们来用代码演示一下流的生成

    public class StreamCreateDemo {
    
        public static void main(String[] args) {
            streamCreateTest();
        }
    
        private static void streamCreateTest(){
            // List集合转Stream流
            List<String> list = new ArrayList<>();
            Stream<String> listStream = list.stream();
    
            // Set集合转Stream流
            Set<Integer> set = new HashSet<>();
            Stream<Integer> setStream = set.stream();
    
            // Map集合间接转Stream流
            Map<String,Integer> map = new HashMap<>();
            Stream<String> keyStream = map.keySet().stream();
            Stream<Integer> valueStream = map.values().stream();
    
            // 数组转Stream流,使用 Stream接口中的静态方法 of(T ...values)
            String[] arr = new String[5];
            Stream<String> arrStream1 = Stream.of(arr);
            Stream<String> arrStream2 = Stream.of("10","20","13");
        }
    }
    
    (二)Stream流的中间操作方法

    中间操作的意思是,执行完此方法之后,Stream流依然可以继续执行其他操作.

    方法名 说明
    Stream filter(Predicate predicate) 用于对流中的数据进行过滤
    Stream limit(long maxSize) 返回此流中的元素组成的流,截取前指定参数个数的数据
    Stream skip(long n) 跳过指定参数个数的数据,返回由该流的剩余元素组成的流
    static Stream concat(Stream a, Stream b) 合并a和b两个流为一个流
    Stream distinct() 返回由该流的不同元素(根据Object.equals(Object))组成的流
    Stream sorted() 返回由此流的元素组成的流,根据自然顺序排序
    Stream sorted(Comparator comparator) 返回由该流的元素组成的流,根据提供的Comparator进行排序
    Stream map(Function mapper) 返回由给定函数应用于此流的元素的结果组成的流
    lntStream mapTolnt(TolntFunction mapper) 返回一个Intstream其中包含将给定函数应用于此流的元素的结果

    下面我们就来演示一下吧

    filter方法

    要求过滤出Stream流中以胡开头,字符长度大于2的元素

    public class StreamCreateDemo {
    
        public static void main(String[] args) {
            List list = Arrays.asList("胡宗宪","胡萝卜","胡说","大胡子");
            filterTest(list);
        }
    
        private static void filterTest(List<String> list){
            list.stream().filter(s->s.startsWith("胡")&&s.length()>2).forEach(System.out::println);
        }
    }
    
    limit和skip方法

    要求跳过Stream流中中前两个元素,只取接下来后面的两个元素

    public class StreamCreateDemo {
    
        public static void main(String[] args) {
            List list = Arrays.asList("胡宗宪","刘亦菲","流光溢彩","胡萝卜","胡说","大胡子");
            skipAndLimitTest(list);
        }
    
        private static void skipAndLimitTest(List<String> list){
            list.stream().skip(2).limit(2).forEach(s -> System.out.println(s));
        }
    }
    
    concat和distinct方法

    要求将两个流进行合并,然后去重后进行打印输出

    public class StreamCreateDemo {
    
        public static void main(String[] args) {
            List list1 = Arrays.asList(10,20,30,40);
            List list2 = Arrays.asList(30,40,50,60);
            concatAndDistinctTest(list1,list2);
        }
    
        private static void concatAndDistinctTest(List<Integer> list1,List<Integer> list2){
            Stream listStream1 = list1.stream();
            Stream listStream2 = list2.stream();
            Stream.concat(listStream1,listStream2).distinct().forEach(System.out::println);
        }
    }
    
    sort方法

    要求:将Stream流中的元素根据字符长度进行(正序)排序,如果长度相等,则按照字母大小排序。

    public class StreamCreateDemo {
    
        public static void main(String[] args) {
            List list1 = Arrays.asList("张国荣","刘翔","黄志明","于谦");
            sortTest(list1);
        }
    
        private static void sortTest(List<String> list){
            // comparingInt表示将比较括号内的两个元素的字符长度
            list.stream().sorted(Comparator.comparingInt(String::length).thenComparing((s1,s2) -> (s1.compareTo(s2)>0?-1:1))).forEach(System.out::println);
        }
    
    map和mapToInt方法

    mapmapInt方法和上面的其他方法略有不同,因为其他方法只是对流中的元素进行过滤、排序等操作,并不会真正改动到流中的元素内容,而map就可以实现改变流中元素
    要求将Stream流中的字符串元素转为整形元素进行打印输出

    public class StreamCreateDemo {
    
        public static void main(String[] args) {
            List list1 = Arrays.asList("124","13","134","163");
            mapTest(list1);
        }
    
        private static void mapTest(List<String> list){
            list.stream().map(Integer::parseInt).forEach(System.out::println);
        }
    }
    

    要求将Stream流中的字符串元素转为整形元素后进行求和操作

    public class StreamCreateDemo {
    
        public static void main(String[] args) {
            List list1 = Arrays.asList("124","13","134","163");
            mapIntTest(list1);
        }
    
        private static void mapIntTest(List<String> list){
            System.out.println(list.stream().mapToInt(Integer::parseInt).sum());
        }
    }
    
    (三)Stream流的终止操作

    终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作。

    常用方法
    方法名 说明
    void forEach(Consumer action) 对此流的每个元素执行操作
    long count() 返回此流中的元素数

    下面就用一个小案例来演示一下吧

    public class StreamCreateDemo {
    
        public static void main(String[] args) {
            List list1 = Arrays.asList("韦小宝","郑成功","王多鱼","刘翔");
            countTest(list1);
        }
    
        private static void countTest(List<String> list){
            long count = list.stream().filter(s -> s.length() > 2).count();
            System.out.println(count);
        }
    }
    

    四、Stream流的收集操作

    流的收集操作是指:我们对数据使用Stream流的方式操作完毕后,可以把流中的数据收集到集合中。

    常用方法
    方法名 说明
    R collect(Collector collector) 把结果收集到集合中

    我们可以看到,collect方法需要我们传递一个Collector对象,但Collector是一个接口,所以我们一般会借助工具类Collectors来帮助我们实现收集的动作。

    工具类Collectors提供了具体的收集方式
    方法名 说明
    public static Collector toList() 把元素收集到List集合中
    public static collector toSet() 把元素收集到set集合中
    public static Collector toMap(Function keyMapper,Function valueMapper) 把元素收集到Map集合中

    下面我们就用一个小案例来演示一下如何使用Stream流的收集操作吧

    public class StreamCreateDemo {
    
        public static void main(String[] args) {
            List list1 = Arrays.asList("韦小宝","郑成功","王多鱼","刘翔","韦小宝");
            List list2 = Arrays.asList("韦小宝,12","郑成功,41","王多鱼,12","刘翔,45","大宝,41");
            collectToSet(list1);
            collectToList(list1);
            collectToMap(list2);
        }
    
        private static void collectToSet(List<String> list){
            Set<String> set = list.stream().filter(s -> s.length() > 2).collect(Collectors.toSet());
            System.out.println(set);
        }
        private static void collectToList(List<String> list){
            List<String> result = list.stream().filter(s -> s.length() > 2).collect(Collectors.toList());
            System.out.println(result);
        }
        private static void collectToMap(List<String> list){
            Map<String,Integer> map = list.stream().filter(s -> s.length() > 2).collect(Collectors.toMap(s -> s.split(",")[0],s -> Integer.parseInt(s.split(",")[1])));
            for(String key : map.keySet()){
                Integer num = map.get(key);
                System.out.println(key + " : " + num);
            }
        }
    }
    

    五、注意事项

    1. 流不能被重复使用,流关闭后也不能再被使用
    流不能重复使用
    2. 流的每个中间操作都会返回一个全新的流对象,直到遇到及早求值(终止操作)才会执行得到最终结果,如果没有终止操作,则所有中间操作根本不会执行。

    至此有关Stream流的介绍就到这里了,Stream流的使用可以在一定程度上减少减少我们在工作中的代码开发量,学习的成本相当来说也不高,个人觉得Stream流的应用属于java开发人员应该要掌握的知识点之一。

    参考文章:
    Stream流介绍及实例:
    https://www.jianshu.com/p/43f853c3c5d3

    相关文章

      网友评论

          本文标题:JDK新特性(五)——Stream流

          本文链接:https://www.haomeiwen.com/subject/ngswhltx.html