• 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
        }
    

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

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

  • 相关阅读:
    堆栈学习
    需要阅读的书籍
    Rust Book Lang Ch.19 Fully Qualified Syntax, Supertraits, Newtype Pattern, type aliases, never type, dynamic sized type
    Rust Lang Book Ch.19 Placeholder type, Default generic type parameter, operator overloading
    Rust Lang Book Ch.19 Unsafe
    Rust Lang Book Ch.18 Patterns and Matching
    Rust Lang Book Ch.17 OOP
    Rust Lang Book Ch.16 Concurrency
    Rust Lang Book Ch.15 Smart Pointers
    HDU3966-Aragorn's Story-树链剖分-点权
  • 原文地址:https://www.cnblogs.com/envythink/p/12871849.html
Copyright © 2020-2023  润新知