zoukankan      html  css  js  c++  java
  • java1.8新特性之stream

    什么是Stream?

    Stream字面意思是流,在java中是指一个来自数据源的元素队列并支持聚合操作,存在于java.util包中,又或者说是能应用在一组元素上一次执行的操作序列。(stream是一个由特定类型对象组成的一个支持聚合操作的队列。)注意Java中的Stream并不会存储元素,而是按需计算。关于这个概念需要以下几点解释:1、数据源流的来源。 它可以是列表,集合,数组(java.util.Collection的子类),I/O channel, 产生器generator等(注意Map是不支持的);2、聚合操作。类似于SQL语句一样的操作, 如filter, map, reduce, find, match, sorted等。因此stream流和以前的Collection操作是完全不同, Stream操作还有两个非常基础的特征:Pipelining和内部迭代。

    Pipelining也就是中间操作,它都会返回流对象本身。 这样多个操作的设计可以串联起不同的运算操作,进而形成一个管道, 如同流式风格(fluent style)。 这样做还可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)等。内部迭代, 以前对集合遍历都是通过Iterator或者For-Each的方式来显式的在集合外部进行迭代, 这种方式叫做外部迭代。而我们的Stream则提供了内部迭代方式, 是通过访问者模式(Visitor)来实现的。

    也就是说Stream操作分为中间操作和最终操作两种。其中最终操作用于返回特定类型的计算结果,而中间操作则返回Stream对象本身,这样就可以将多个操作依次串起来且使得操作优化成为可能。

    生成流

    在Java1.8 中, 集合接口提供了两个方法来生成流:stream()串行流parallelStream()并行流,即Stream的操作可以分为串行stream()和并行parallelStream()。举个例子来说:

    List<String> strings = Arrays.asList("who","what","when","why","which");
    List<String> filterd = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
    

    流的各种运算操作

    接下来介绍流的各种操作运算,使得你在适当的时候可以选择相应的流运算。

    1、forEach 循环

    Stream提供了新的方法forEach来迭代流中的每个数据。举个例子来说:

           List<String> stringList = Arrays.asList("who","what","when","why","which");
    
            // 方式一:JDK1.8之前的循环方式
            for(String string:stringList){
                System.out.println(string);
            }
    
            // 方式二:使用Stream的forEach方法
            stringList.stream().forEach(e -> System.out.println(e));
    
            // 方式三:方式二的简化形式,因为方法引用也属于函数式接口,因此Lambda表达式可以用方法引用来代替
            stringList.stream().forEach(System.out::println);
    

    2、filter 过滤

    filter方法用于通过设置条件来过滤出满足条件的元素。举个例子来说,下面就是用于输出字符串列表中的空字符串的个数:

            List<String> stringList = Arrays.asList("","welcome","","to","visit","my","","website");
            long count = stringList.stream().filter(e -> e.isEmpty()).count();
            System.out.println(count);
    

    3、map 映射

    请注意这里的map不是指地图map,而是一种函数,用于映射每个元素执行某些操作得到对应的结果。举个例子来说,下面就是使用map来输出元素对应的平方数:

            List<Integer> integerList = Arrays.asList(2,3,4,5,6);
            List<Integer> integers = integerList.stream().map(i->i*i).collect(Collectors.toList());
            integerList.stream().forEach(System.out::println);
    

    上面介绍的只是map的最基本用法。map对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法,分别是:mapToIntmapToLongmapToDouble。顾名思义像mapToInt就是将原始Stream转换成一个新的Stream,不过新生成的Stream中的元素都是int类型。三个变种方法可以免除自动装箱/拆箱的额外消耗。map方法示意图:

    4、flatMap 映射

    flatMap映射和map映射类似,不过它的每个元素转换得到的是Stream对象,会把子Stream中的元素压缩到父集合中,说白了就是将几个小的list合并成一个大的list。flatMap方法示意图:

    合并的过程可以参看下面这张图片:

    举个例子来说,下面是jdk1.8之前的合并方式,需要先构造一个复合类型List,然后通过两次遍历循环来实现将复合类型List转为单一类型List,这个过程其实挺复杂的:

            List<String> fruitList = Arrays.asList("banana","orange","watermelon");
            List<String> vegetableList = Arrays.asList("kale","leek","carrot");
            List<String> transportList = Arrays.asList("car","bike","train");
    
            //将多个元素合成一个复合类型集合,元素类型List<String>
            List<List<String>> lists = new ArrayList<>();
            lists.add(fruitList);
            lists.add(vegetableList);
            lists.add(transportList);
    
            //将多个元素合成一个单一类型集合,元素类型String
            List<String> newList = new ArrayList<>();
            for(List<String> list:lists){
                for(String item:list){
                    newList.add(item);
                }
            }
    

    那么使用jdk1.8提供的stream流,同时辅助of、collect和flatMap就可以直接进行转换:

            List<String> fruitList = Arrays.asList("banana","orange","watermelon");
            List<String> vegetableList = Arrays.asList("kale","leek","carrot");
            List<String> transportList = Arrays.asList("car","bike","train");
            
            //将多个元素合成一个复合类型集合,元素类型List<String>
            List<List<String>> lists = Stream.of(fruitList,vegetableList,transportList).collect(Collectors.toList());
    
            //将多个元素合成一个单一类型集合,元素类型String
            List<String> flatMap = Stream.of(fruitList,vegetableList,transportList)
                    .flatMap(list ->list.stream())
                    .collect(Collectors.toList());
            System.out.println(flatMap);
    

    5、sorted 排序

    sorted方法用于对流进行排序。举个例子来说,下面的代码就是用于对字符串按照给定的规则进行排序并输出:

            List<String> stringList = Arrays.asList("c","a","f","d","b","e");
            stringList.stream().sorted((s1,s2) -> s1.compareTo(s2)).forEach(System.out::println);
    

    再举个例子,对10个随机数进行排序并输出:

            Random random = new Random();
            random.ints().limit(10).sorted().forEach(System.out::println);
    

    6、distinct 去除重复

    distinct方法用于去除流中重复的元素,缺点就是不能设置去重的条件。举个例子来说:

            List<String> stringList = Arrays.asList("do","what","you","want","to","do","and","do","it");
            stringList.stream().distinct().forEach(System.out::println);
    

    7、of 生成Stream对象

    of方法用于生成Stream对象,注意它是Stream对象的方法。举个例子来说:

            Stream<Object> objectStream= Stream.of("do","what","you","want","to","do","and","do","it");
            objectStream.forEach(System.out::println);
    

    8、count 计算总数

    count方法用于计算流中元素的总数。举个例子来说:

            Stream<Object> objectStream = Stream.of("do","what","you","want","to","do","and","do","it");
            long count = objectStream.count();
            System.out.println(count);
    

    9、min和max 最小/最大

    min/max方法用于返回流中那个元素最小(最大)的,注意返回的是一个Optional对象。举个例子来说:

            List<String> integerList = Arrays.asList("1","2","3","4","5","6","7");
            Optional<String> optionalInteger = integerList.stream().max((a,b) -> a.compareTo(b));
            String result =  optionalInteger.get();
            System.out.println(result);  //结果为7
    

    10、collect

    collect方法的使用较为复杂,这里仅仅介绍一些常用的方法即可。collect方法可以将Stream转为Collection对象或者是Object类型的数组等,举个例子来说:

            List<String> stringList= Arrays.asList("do","what","you","want","to","do","and","do","it");
            //Stream转Collection
            stringList.stream().collect(Collectors.toList());
            //Stream转Object[]数组
            Object[] objects = stringList.stream().toArray();
    

    11、concat

    concat方法用于合并流对象,注意这时Stream对象的方法。举个例子来说:

            List<String> fruitList = Arrays.asList("banana","orange","watermelon");
            List<String> vegetableList = Arrays.asList("kale","leek","carrot");
    
            Stream<String> stringStream = Stream.concat(fruitList.stream(),vegetableList.stream());
            stringStream.forEach(System.out::println);
    

    12、skip和limit

    通常大家都会将skip和limit放在一块进行学习和对比,那是因为两者具有类似的作用,都是对流进行裁剪的中间方法。

    skip方法。先来看skip方法,顾名思义skip(n)用于跳过前面n个元素,然后再返回新的流,如图所示:

    为了验证上面图片的作用,这里举一个例子来进行说明:

    public static void skipTest(long n){
            Stream<Integer> integerStream = Stream.of(1,2,3,4,5,6);
            integerStream.skip(n).forEach(System.out::println);
        }
    

    方法skip()中的参数n不同将会导致不同的结果,具体情况如下:
    (1)、当n<0时,运行结果会抛出IllegalArgumentException异常;(2)、当n=0时,相当没有跳过任何元素,原封不动地截取流中的元素(这种通常没有意义,基本不会这样操作);(3)、当0<n<length时,表示跳过n个元素后(不包括元素n),结果返回含有剩下的元素的流(使用频率较多);(4)、当n>=length时,表示跳过所有元素,结果返回空流,你可以使用count方法来判断此时流中元素的总数必定为0。

    limit方法。说完了skip()方法,接下来聊聊limit()方法。顾名思义这个就是限制流中的元素,即用于将前n个元素返回新的流,如图所示:

    同样也通过举一个例子来进行说明:

     public static void limitTest(long n){
            Stream<Integer> integerStream = Stream.of(1,2,3,4,5,6);
            integerStream.limit(n).forEach(System.out::println);
        }
    

    方法limit()中的参数n不同将会导致不同的结果,具体情况如下:
    (1)、当n<0时,运行结果会抛出IllegalArgumentException异常;(2)、当n=0时,相当不取元素,结果返回空流;(3)、当0<n<length时,表示取前n个元素,结果返回新的流(使用频率较多);(4)、当n>=length时,表示取所有元素,结果返回流本身,你可以使用count方法来判断此时流中元素的总数必定为length。

    区别:注意这里谈skiplimit方法的区别是局限于有限流skiplimit方法都是对流进行截取操作,区别在于skip方法必须时刻监测流中元素的状态,才能判断是否需要丢弃,因此skip属于状态操作。而limit只关心截取的是否是其length,是就立马中断操作返回流,因此limit属于中断操作。

    13、并行(parallel)执行

    parallelStream是流并行处理程序的代替方法。举个例子来说,下面是使用 parallelStream并行流来输出空字符串的数量:

            List<String> stringList= Arrays.asList("a","","b","","e","","c","","f");
            //获取空字符串的数量
            long count = stringList.parallelStream().filter(string -> string.isEmpty()).count();
            System.out.println(count);  // 4
    

    14、anyMatch、allMatch和noneMatch

    anyMatch方法用于判断流中是否存在满足特定条件的元素,返回类型是boolean类型。(只要有一个条件满足即返回true)

            List<String> stringList = Arrays.asList("hello","the","fruit","name","is","banana");
            Boolean result = stringList.parallelStream().anyMatch(item -> item.equals("name"));
            System.out.println(result);  // true
    

    allMatch方法用于判断流中是否存在满足特定条件的元素,返回类型是boolean类型。(必须全部满足才会返回true)

            List<String> stringList = Arrays.asList("hello","the","fruit","name","is","banana");
            Boolean result = stringList.parallelStream().allMatch(item -> item.equals("name"));
            System.out.println(result);  // false
    

    noneMatch方法用于判断流中是否存在满足特定条件的元素,返回类型是boolean类型。(全都不满足才会返回true)

            List<String> stringList = Arrays.asList("hello","the","fruit","name","is","banana");
            Boolean result = stringList.parallelStream().noneMatch(item -> item.equals("name"));
            System.out.println(result);  // false
    

    上面这个例子就是因为有一个满足条件就返回了false。

    15、reduce

    reduce的意思是减少,而Stream中reduce方法就是用于实现这个目的,它根据一定的规则将Stream中的元素进行计算后返回一个唯一的值。举个例子来说:

            Stream<String> stringStream = Stream.of("my","name","is","envy");
            Optional<String> stringOptional = stringStream.reduce((before, after) -> before+"、"+after);
            stringOptional.ifPresent(System.out::println);  // my、name、is、envy
    

    16、findFirst和findAny

    findFirst方法用于返回list列表中第一个元素,注意如果元素不存在则抛异常。举个例子来说:

            List<String> stringList = Arrays.asList("do","what","you","want","to","do","and","do","it");
            Optional<String> result = stringList.parallelStream().findFirst();
            System.out.println(result.get());  // do
    

    注意若Optional为空,则get方法会抛出异常,但是你可以使用orElse(defaultVal);或使用orElseGet(() -> {// doSomething; return defaultVal;});来返回默认值。举个例子来说:

            List<String> stringList = Arrays.asList();
            Optional<String> result = stringList.parallelStream().findFirst();
            System.out.println(result.orElse("没有元素"));  // 没有元素
    
            List<String> stringList = Arrays.asList();
            Optional<String> result = stringList.parallelStream().findFirst();
            System.out.println(result.orElseGet(() ->{return "没有元素";}));  // 没有元素
    

    17、summaryStatistics统计

    summaryStatistics方法用于产生统计结果的收集器,举个例子来说:

            List<Integer> integerList = Arrays.asList(3,2,3,5,6,8,9);
            IntSummaryStatistics result = integerList.stream().mapToInt((x)->x).summaryStatistics();
            System.out.println("列表中最大的数:"+result.getMax());
            System.out.println("列表中最小的数:"+result.getMin());
            System.out.println("列表中所有数之和:"+result.getSum());
            System.out.println("列表中所有数的平均数:"+result.getAverage());
            System.out.println("列表中元素的个数:"+result.getCount());
    

    18、Joining集合元素的拼接

    集合元素的拼接,其实就是指定分隔符将列表中的元素合并成一个字符串,注意joining方法是存在于Collectors中的。举个例子来说:

            List<String> stringList = Arrays.asList("my","name","is");
            System.out.println(stringList);  // [my, name, is]
    
            String result = stringList.stream().collect(Collectors.joining(","));
            System.out.println(result);  // my,name,is
    
            String newString = Stream.of("I","come","from bei").collect(
                    Collectors.collectingAndThen(
                            Collectors.joining(","),x-> x+"jing"));
            System.out.println(newString);  // I,come,from beijing
    

    19、Collectors之流转换成集合

    Collectors类实现了很多归约操作,例如将流转换成集合和聚合元素等,Collectors 可用于返回列表或字符串。下面通过举一些经常会使用到的例子来进行说明:
    先在外部新建一个Student实体类,后续会使用到:

    public class Student {
        private String name;
        private Long score;
       //getter/setter/toString/AllArgsConstructor
    }
    

    然后看下面的例子代码:

            List<Integer> integerList = Arrays.asList(1,2,3,4,5);
            //流转列表
            List<Integer> newList = integerList.stream().map(i -> i*10).collect(Collectors.toList());
            System.out.println("新列表:"+newList);  //[10, 20, 30, 40, 50]
    
            //流转集合
            Set<Integer> integerSet = integerList.stream().map(i -> i*10).collect(Collectors.toSet());
            System.out.println("新集合:"+integerSet);  //[50, 20, 40, 10, 30]
    
            //流转映射
            Map<String,String> stringStringMap = integerList.stream().map(i ->i*10).collect(
                    Collectors.toMap(key -> "key"+key/10,value -> "value:"+value)
            );
            System.out.println("新映射:"+stringStringMap);  //{key1=value:10, key2=value:20, key5=value:50, key3=value:30, key4=value:40}
    
    
            //流转有序集合TreeSet
            TreeSet<Integer> integerTreeSet = Stream.of(1,6,3,7,2).collect(Collectors.toCollection(TreeSet::new));
            System.out.println("新有序集合:"+integerTreeSet);  //[1, 2, 3, 6, 7]
    
    
            //自定义对象流
            List<Student> studentList = Arrays.asList(
                    new Student("envy",100L),
                    new Student("movie",90L),
                    new Student("book",80L)
            );
    
            //获得对象
            Map<String,Student> studentAndModelMap = studentList.stream().collect(Collectors.toMap(
                    Student::getName, Function.identity()
            ));
            Student student = studentAndModelMap.get("envy");
            System.out.println(student);  //Student{name='envy', score=100}
    
            //获得属性
            Map<String,Long> studentAndStudentScoreMap = studentList.stream().collect(Collectors.toMap(
                    Student::getName, Student::getScore
            ));
            Long score = studentAndStudentScoreMap.get("envy");
            System.out.println(score);  //100
    

    20、Collectors之元素聚合

    其实这个元素聚合归根结底还是Collectors类中的方,下面就来介绍聚合元素这个操作,Collectors 可用于返回列表或字符串。下面通过举一些经常会使用到的例子来进行说明:

            //元素聚合
    
            List<Integer> integerList = Arrays.asList(1,5,8,3,6,2,9,7,4);
    
            //求最大值
            Integer maxValue = integerList.stream().collect(
                    Collectors.collectingAndThen(
                            Collectors.maxBy((a,b) -> a-b), Optional::get
                    ));
            System.out.println(maxValue);  // 9
    
            //求最小值
            Integer minValue = integerList.stream().collect(
                    Collectors.collectingAndThen(
                            Collectors.minBy((a,b) -> a-b), Optional::get
                    ));
            System.out.println(minValue);  // 1
    
            //求和
            Integer sumValue = integerList.stream().collect(
                    Collectors.summingInt(item ->item)
            );
            System.out.println(sumValue);  // 45
    
            //平均值
            Double avgValue = integerList.stream().collect(
                    Collectors.averagingDouble(x -> x)
            );
            System.out.println(avgValue);  // 5.0
    
    
            //集合转映射
            String listToMap = Stream.of("my","name","is","envy").collect(
                    Collectors.mapping(
                            x->x.toUpperCase(),Collectors.joining(",")
                    )
            );
            System.out.println(listToMap);  // MY,NAME,IS,ENVY
    

    21、累计操作

    reducing累计操作,也是Collectors类中的方法,用于进行元素的累计操作。先来看一个例子,用于计算出[2,3,5,6]这个列表中所有元素各加1之后的所有元素之和,很简单口算都可以知道答案是20。你可能有很多种想法,这里提供几种以供你参考:

            //方法一,不使用stream
            int[] ints = {2,3,5,6};
            int resultSum =0;
            for(int i:ints){
                i++;
                resultSum+=i;
            }
            System.out.println(resultSum);  //20
    
            //方法二,使用stream流的map+summingInt方法
            List<Integer> integerList = Arrays.asList(2,3,5,6);
            Integer integerOne = integerList.stream().map(i ->i+1).collect(
                    Collectors.summingInt(x ->x)
            );
            System.out.println(integerOne);  //20
    
    
            //方法三,使用stream流的reducing方法
            Integer integerTwo = integerList.stream().collect(
                    Collectors.reducing(0,x->x+1,(sum,b) -> {
                        return sum+b;
                    })
            );
            System.out.println(integerTwo);  //20
    
    
            // reducing还可以用于更复杂的累计计算,不局限于加减乘除等操作
            Integer integerThree = integerList.stream().collect(
                    Collectors.reducing(1,x->x+1,(result,b) -> {
                        return result*b;
                    })
            );
            System.out.println(integerThree);  // 3*4*6*7=504
    

    22、groupingBy 分组

    groupingBy分组这个功能在实际开发中用的非常多,因此有必须要好好用一下,它也是存在于Collectors类中的。来看一下这个Collectors.groupingBy方法的源码,它有三个重载方法,这里就以只有一个参数的方法为例进行说明:

    public static <T, K> Collector<T, ?, Map<K, List<T>>>
        groupingBy(Function<? super T, ? extends K> classifier) {
            return groupingBy(classifier, toList());
        }
    

    可以发现它的参数只有一个:Function<? super T, ? extends K> classifier,类型是Function类型也就是个函数,Function的返回值可以是要分组的条件,或者是要分组的字段。groupingBy方法的返回的结果是一个Map,其中key的数据类型为Function体中的计算类型(也就是参数类型),value是List类型也就是分组的结果。

    接下来通过一个例子来介绍如何使用它,这个例子也非常简单,给定[1,2,3,4,5,6,7,8,9]这个列表,如何将其按照奇数和偶数来划分为两组。用以往的知识你可能会这样操作:

            List<Integer> oneList = new ArrayList<>();  //奇数列表
            List<Integer> twoList = new ArrayList<>();  //偶数列表
            for(Integer item:integerList){
                if(item%2==0){
                    twoList.add(item);
                }else {
                    oneList.add(item);
                }
            }
            System.out.println(oneList);  // [1, 3, 5, 7, 9]
            System.out.println(twoList);  // [2, 4, 6, 8]
    

    但是如果你使用了stream那就变得简单多了:

            //方法二,使用stream
            Map<Boolean, List<Integer>> resultMap = integerList.stream().collect(
                    Collectors.groupingBy(item -> item%2 ==0)
            );
            System.out.println(resultMap);  // {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8]}
    
            Map<Boolean, List<Integer>> twoPartition  = integerList.stream().collect(
                    Collectors.partitioningBy(item -> item%2 ==0)
            );
            System.out.println(twoPartition);  //twoPartition就是将结果为为两组
    
    
            //还可以自定义分组的条件
            List<Student> studentList = Arrays.asList(
                    new Student("book",100L,1),
                    new Student("movie",90L,2),
                    new Student("fruit",80L,2),
                    new Student("vegetable",70L,4)
            );
            //根据某个字段进行分组
            Map<Integer,List<Student>> studentMap = studentList.stream().collect(
                    Collectors.groupingBy(item ->item.getId())
            );
            System.out.println(studentMap);
            //{1=[Student{name='book', score=100}], 2=[Student{name='movie', score=90}, Student{name='fruit', score=80}], 4=[Student{name='vegetable', score=70}]}
    
            //还可以结合前面的统计结果处理器来对结果进行分析
            Map<Integer, LongSummaryStatistics> summaryStatisticsMap = studentList.stream().collect(
                    Collectors.groupingBy(
                            Student::getId, Collectors.summarizingLong(Student::getScore)
                    )
            );
    
            LongSummaryStatistics statisticsOne = summaryStatisticsMap.get(1);
            LongSummaryStatistics statisticsTwo = summaryStatisticsMap.get(2);
    
            System.out.println(statisticsOne.getMax());  //100
            System.out.println(statisticsOne.getMin());  //100
            System.out.println(statisticsOne.getAverage());  //100.0
            System.out.println(statisticsOne.getCount());  //1
            System.out.println(statisticsOne.getSum());  //100.0
    
            System.out.println("*********");
            System.out.println(statisticsTwo.getMax());  //90
            System.out.println(statisticsTwo.getMin());  //80
            System.out.println(statisticsTwo.getAverage());  //85.0
            System.out.println(statisticsTwo.getCount());  //2
            System.out.println(statisticsTwo.getSum());  //170
        }
    

    上面基本上把日常开发过程中可能会遇到的场景都进行了介绍,但是我觉得这是做了第一步如何使用它,后续会出一些文章来好好研究里面的源码,同时会对上面的一些方法进行更深层次的研究和使用。

    获取更多内容请关注个人微信公众账号:余思博客,或者微信扫描下方二维码即可直接关注:

  • 相关阅读:
    【转】C++11优化使用emplace,emplace_back
    面试经历总结
    Hive常用函数
    股票指标
    Visual Studio Code 可以翻盘成功主要是因为什么?
    openpyxl模块操作Excel
    JavaScript(二)
    前端之CSS
    ps导出ICO格式
    Qt 所有版本官方下载地址
  • 原文地址:https://www.cnblogs.com/envythink/p/12871849.html
Copyright © 2011-2022 走看看