Stream API

发布于 2022-06-07  4.32k 次阅读


一,Stream的概述

Java8中提出的最重要的两个特性,一个是Lambda+函数式接口、第二个就是Stream API(java.util.stream.*),它标志着java做为面向对象编程语言,集成了函数式的编程方式

  Stream是处理集合的抽象概念,它不仅可以对指定的集合进行任意常规化操作还可以进行复杂的查找,过滤和映射数据等操作。Stream对集合的操作就和Scala中的集合基本操作一样是一种高效便捷处理数据的方式

Stream流的具体含义:它是一个中间数据渠道,用于操作数据源(集合、数组等)所产生的元素序列,集合存储数据,流计算数据

Stream的特性:

  • Stream本身不存储数据
  • Stream不会改变源对象数据。在非终止操作的其他操作它会返回一个持有结果的新Stream
  • Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行(也就是执行终止操作时才全部执行)

Stream操作的三大步骤:

  1. 创建Stream:创建Stream有四种方式
  2. 中间操作:一个中间操作链,对数据源的数据进行处理
  3. 终止操作:终止操作触发所有的中间操作,并产生结果

二,Stream的四种创建方式

四种方式:

  1. 通过Collection系列集合提供的stream()或parallelStream()方法获取
  2. 通过Arrays中的静态方法stream()获取数组流
  3.  通过Stream类中的静态方法of()获取
  4. 通过Steam类中的静态方法iterate()和generate()获取无线流

注:在java8中Collection集合顶级接口被拓展,加入stream()和parallelStream()两个获取流方法

  • stream():获取串行流
  • parallelStream():获取并行流

无限流:

  • iterate():通过迭代的方式
  • generate():通过直接生成的方式

例:

//1,集合的stream()方法获取
List<String> list = new ArrayList<>();
Stream stream1 = list.stream();

//2,通过Arrays的静态方法Stream获取数组流
String[] str = new String[10];
Stream<String> stream2 = Arrays.stream(str);

//3,通过Stream中静态方法of()
Stream<Integer> stream3 = Stream.of(1,2,3);

//4,通过Stream中静态方法创建无限流
//4.1 iterate迭代方式
Stream stream4 = Stream.iterate(0, (x) -> x+2);
stream4.forEach(System.out::println);
//4.2 generate直接生产
Stream stream5 = Stream.generate(() -> Math.random()*10000000);
stream5.forEach(System.out::println);
具体参考官方java8 API

三,Stream的中间操作

把Stream比做成流水线,那么那么中间操作是数据处理的中间过程,中间操作的返回值为Stream,多个Stram相互连接形成处理链,它采用"惰性求值"方式

惰性求值:如果Stream流式处理链没有执行终止操作,那么所有的中间操作都不会被触发

中间操作按功能分为三类:

  1. 切片筛选
  2. 映射
  3. 排序

特点:

  • 内部迭代
  • 惰性求值

一,切片筛选

方法 描述
filter(Predicate p) 接收Lambda,从流中排除元素
distinct() 去重,使用它必须重写hashCode()和equals()方法
limit(long maxSize) 截断流,返回指定数量的元素
skip(long n) 跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补

例:

 public static void main(String[] args) {
             List<User> users = Arrays.asList(
                           new User(5,"王五",23,6091.2),
                           new User(2, "程二", 25,4504.342),
                           new User(3,"张三",29,12010.21),
                           new User(4,"李四",30,8763.21),
                           new User(5,"王五",23,6091.2));
             
             //1,filter获取工资大于6000的员工
             System.out.println("---------------------filter条件筛选---------------------");
             Stream<User> stream1 = users.stream().filter((x) -> x.getMoney() > 6000.0);//内部迭代
             stream1.forEach(System.out::println); //终止操作
             
             //2,distinct去重年  --> 注:在使用前在User类中重写equals()和hashCode()方法,不然不能判断去重
             System.out.println("---------------------distinct去重---------------------");
             Stream<User> stream2 = users.stream().distinct();
             stream2.forEach(System.out::println); //终止操作
             
             //3,limit截断,只获取工资6000的两个员工
             System.out.println("---------------------limit截断---------------------");
             users.stream()
                     .filter((x) -> x.getMoney() > 6000.0)
                     .limit(2)
                     .forEach(System.out::println);
             
             //4,skip跳过集合前3个元素
             System.out.println("---------------------skip跳过---------------------");
             users.stream()
                     .skip(3)
                     .forEach(System.out::println);
   }

结果:

二,映射

方法 描述
map(Function mapper) 接收Lambda,将元素转化为其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成新的元素
flatMap() 接收一个函数做为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个新流

map和flatMap的区别就相当于集合中add()和addAll():

  • map:将stream流整体加入另一个stream中
  • flatMap:将stream流中的数据或对象加入到另一个stream中

例1:map方法演示

public static void main(String[] args) {
             
       Stream<User> stream1 = Arrays.asList(
                    new User(5,"王五",23,6091.2),
                    new User(2, "程二", 25,4504.342),
                    new User(3,"张三",29,12010.21),
                    new User(4,"李四",30,8763.21),
                    new User(5,"王五",23,6091.2)).stream();
             
       Stream<String> stream2 =  Arrays.asList("aa","bb","cc","dd","ee").stream();
       
       //例1:map将流中字符全部转大写  --> 参数为function函数式接口
       stream2.map((x) -> x.toUpperCase())
                .forEach(System.out::println);
       
       //例2:提取集合中User对象的姓名
       stream1.map((x) -> x.getName())
                .forEach(System.out::println);
             }

例2:map和flatMap的区别:list中每一个元素传入filterCharacter方法分解成Char字符打印

public static void main(String[] args) {
             Stream<String> stream =  Arrays.asList("aaa","bbb","ccc","ddd","eee").stream();
             
             //map方式打印
             System.out.println("-------------------map方式打印-------------------");
             stream.map(StreamCentreMain3::filterCharacter)
                   .forEach((s) -> s.forEach(System.out::println)); //[stream,……,stream]


             //flatMap方式打印
             System.out.println("-------------------flatMap方式打印-------------------");
             stream.flatMap(StreamCentreMain3::filterCharacter)
                      .forEach(System.out::println); [a,a,a,b,b,b……e,e,e]
       
       }
       
       public static Stream<Character> filterCharacter(String str){
             List<Character> list = new ArrayList();
             for(Character c :str.toCharArray()) {
                    list.add(c);
             }
             return list.stream();
       }}

三,排序

方法 描述
sorted() 自然排序
sorted(Comparator com) 定制排序

例:

    public static void main(String[] args) {

       Stream<User> stream1 = Arrays.asList(
                    new User(5,"王五",23,6091.2),
                    new User(2, "程二", 25,4504.342),
                    new User(3,"张三",29,12010.21),
                    new User(4,"李四",30,8763.21),
                    new User(5,"王五",23,6091.2)).stream();
             
       Stream<String> stream2 =  Arrays.asList("ee","aa","cc","dd","bb").stream();
       
       //自然排序
       System.out.println("------------自然排序------------");
       stream2.sorted()
                .forEach(System.out::println);
       //定制排序:按照user的工资进行排序
       System.out.println("------------定制排序------------");
       stream1.sorted((x,y) -> x.getMoney().compareTo(y.getMoney()))
                .forEach(System.out::println);
}}

四,Stream的终止操作

Stream终止操作会从流中生成结果,其结果可以是任何不是流的值,例如:List,Integer,String等,也可以是void比如forEach()

终止操作按功能有两大类:

  • 查找匹配
  • 归约
  • 收集

一,查找匹配

方法 描述
allMatch(Predicate p) 检查是否匹配所有元素
anyMatch(Predicate p) 检查是否至少匹配一个元素
noneMatch(Predicate p) 检查是否没有匹配一个元素
findFirst() 返回第一个元素
findAny() 返回当前流中的任意元素
count() 返回流中元素的总个数
max(comparator com) 返回流中最大值
min(comparator com) 返回流中最小值
forEach(Consumer c) 内部迭代

例:

public static void main(String[] args) {
        List<User> users = Arrays.asList(
                      new User(5,"王五",23,6091.2),
                      new User(2, "程二", 25,4504.342),
                      new User(3,"张三",29,12010.21),
                      new User(4,"李四",30,8763.21),
                      new User(5,"王五",23,6091.2));
        
        //1,检查是否匹配所有元素
        System.out.println("------------匹配所有元素------------");
        boolean a1 = users.stream().allMatch((x) -> x.getId()==5);
        System.out.println(a1);

        //2,匹配至少一个元素
        System.out.println("------------匹配至少一个元素------------");
        boolean a2 = users.stream().anyMatch((x) -> x.getName().equals("王五"));
        System.out.println(a2);

        //3,是否没有匹配元素
        System.out.println("------------是否没有匹配元素------------");
        boolean a3 = users.stream().noneMatch((x) -> x.getId()==5);
        System.out.println(a3);

        //4,返回流中的第一个元素
        System.out.println("------------返回流中的第一个元素------------");
        Optional<User> a4 = users.stream().findFirst();
        System.out.println(a4.get());

        //5,返回流中的任意元素
        System.out.println("------------返回流中的任意元素------------");
        Optional<User> a5 = users.stream().findAny();
        System.out.println(a5.get());

        //6,返回流中元素的总个数
        System.out.println("------------返回流中元素的总个数------------");
        long a6 = users.stream().count();
        System.out.println(a6);

        //7,返回流中的最大值
        System.out.println("------------返回流中的最大值------------");
        Optional<User> a7 = users.stream().max((x,y) ->  x.getMoney().compareTo(y.getMoney()));
        System.out.println(a7.get());

        //8,返回流中的最小值
        System.out.println("------------返回流中的最小值------------");
        Optional<User> a8 = users.stream().min((x,y) ->  x.getMoney().compareTo(y.getMoney()));
        System.out.println(a8.get());
  }

二,归约

方法 描述
reduce(T identity, BinaryOperator binary)

reduce(BinaryOperator binary)

可以将流中流中元素反复结合起来,得到一个

T identity为初始值:

  • 假如有初始值,结果肯定不为空,就返回类型的值
  • 假如没有初始值,结果可能为空,返回类型就是Optional<T>,这个类型也是java8的新特性,只有可以避免空指针异常
例:
public static void main(String[] args) {
       
             //1,将集合中的数字进行累加
             List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
             Integer i = list.stream().reduce(0,(x,y) -> x+y); //0为x的初始值,每一次x+y的值都重新赋值给x,每一次从流中获取的值赋值给y
             System.out.println(i);
             
             //2,将user的工资进行累加
             List<User> users = Arrays.asList(
                           new User(5,"王五",23,6091.2),
                           new User(2, "程二", 25,4504.342),
                           new User(3,"张三",29,12010.21),
                           new User(4,"李四",30,8763.21),
                           new User(5,"王五",23,6091.2));
             Optional<Double> s = users.stream()
                     .map((x) -> x.getMoney())
                     .reduce(Double::sum); //等价于.reduce((x,y) ->  Double.sum(x,y))
             System.out.print(s.get());
       }

三,收集

方法 描述
collect(Collectors c) 将流转换为其他形式。接收一个Collection接口的实现,用于Stream中元素做汇总的方法

Collector接口中方法的实现决定了如何对流执行收集操作(如收集List,Set,Map)。但是Collectors实用类提供了很多静态方法,可以方便地创建常用收集器实例

Collectors类中的方法:这个类方法的概念包含了之前的中断方法和部分的中间操作

一,元素收集成集合

方法 描述
toCollection(Supplier<C> sup) 返回一个 Collector ,按照遇到的顺序将输入元素累加到一个新的 Collection中
toList() 返回一个 List,它将输入元素 List到一个新的 List
toSet() 将输入元素 Set到一个新的 Set
toMap(Function keyMapper, Function valueMapper) 返回一个 Map,它将元素累加到一个 Map ,其键和值是将所提供的映射函数应用于输入元素的结果
toConcurrentMap(Function keyMapper, Function valueMapper, BinaryOperatormergeFunction) 返回一个并发的 Collector ,它将元素累加到一个 ConcurrentMap ,其键和值是将提供的映射函数应用于输入元素的结果

注:toCollection(Supplier<C> sup)是通用的,传入什么集合类型,就收集成什么集合

例:

public static void main(String[] args) {
     
           List<User> users = Arrays.asList(
                         new User(5,"王五",23,6091.2),
                         new User(2, "程二", 25,4504.342),
                         new User(3,"张三",29,12010.21),
                         new User(4,"李四",30,8763.21),
                         new User(5,"王五",23,6091.2));
           
           //1,将user集合中的名字转成list集合
           System.out.println("------------------toList转list集合------------------");
           List<String> list1 = users.stream()
                                     .map((x) -> x.getName())
                                     .collect(Collectors.toList());
           list1.forEach(System.out::print);
           
           //2,将user集合中的id进行去重保存到set集合中
           System.out.println("\n------------------toSet转set集合------------------");
           Set<Integer> set = users.stream()
                                   .map((x) -> x.getId())
                                   .collect(Collectors.toSet());
           set.forEach(System.out::print);
           
           //3,将user集合中的id和name提取出来形成map集合
           System.out.println("\n------------------toMap转map集合------------------");
           users.stream()
                .collect(Collectors.toMap((x)->x.getId(),  (y)->y.getName(),(x,y)->y))
                .forEach((x,y)->{
                        System.out.print(x+":");
                        System.out.print(y+" ");
                           });
           
           //4,自定义收集user对象中的工资为Vector集合
           System.out.println("\n------------------toCollection自定义转化为Vector集合------------------");
           users.stream()
                   .map((x) -> x.getMoney())
                   .collect(Collectors.toCollection(Vector::new))
                   .forEach(System.out::print);
           
           //5,提取user中的姓名和年龄到ConcurrentMap并法集合中
           System.out.println("\n------------------toConcurrentMap并发Map------------------");
           users.stream()
                   .collect(Collectors.toConcurrentMap((x)->x.getName(),  (y)->y.getAge(),(x,y)->y))
                   .forEach((x,y)->{
                               System.out.print(x+":");
                               System.out.print(y+" ");
                                });
     }

二,元素的计算

方法 描述
averagingDouble(ToDoubleFunction mapper) 产生应用于输入元素的双值函数的算术平均值
averagingInt(ToIntFunction mapper) 产生应用于输入元素的整数值函数的算术平均值
averagingLong(ToLongFunction mapper) 产生应用于输入元素的长值函数的算术平均值
counting() 求数据总个数
maxBy(Comparator comparator) 求最大值
minBy(Comparator  comparator) 求最小值
reducing(T identity, BinaryOperator  op) 将流中流中元素反复结合起来,得到一个值
reducing(BinaryOperator  op) 没有初始值,将流中流中元素反复结合起来,得到一个

summingDouble(ToDoubleFunction mapper) 产生应用于输入元素的双值函数的和
summingInt(ToIntFunction  mapper) 产生应用于输入元素的整数值函数的和
summingLong(ToLongFunction mapper) 产生应用于输入元素的长值函数的和
summarizingDouble(ToDoubleFunction mapper) double生产映射函数应用于每个输入元素,并返回结果值的汇总统计信息
summarizingInt(ToIntFunction mapper) int生产映射函数应用于每个输入元素,并返回结果值的汇总统计信息
summarizingLong(ToLongFunction mapper) ong生产映射函数应用于每个输入元素,并返回结果值的汇总统计信息

一些功能和Stream原生提供的方法有重复,这些重复方法,使用方式是一样,但Collectors对功能做了增强(如:求平均值,汇总信息)

例:

public static void main(String[] args) {
           
           List<User> users = Arrays.asList(
                         new User(5,"王五",23,6091.2),
                         new User(2, "程二", 25,4504.342),
                         new User(3,"张三",29,12010.21),
                         new User(4,"李四",30,8763.21),
                         new User(5,"王五",23,6091.2));
           
           //1,求user中工资的平均值
           System.out.println("------------------求平均值------------------");
           Double avg = users.stream()
                   .collect(Collectors.averagingDouble(User::getMoney));
           System.out.println(avg);
           
           //2,结果值的汇总统计信息,将工资信息进行汇总(DoubleSummaryStatistics为汇总对象),
           System.out.println("------------------结果值的汇总统计信息------------------");
           DoubleSummaryStatistics dou =users.stream()
                   .collect(Collectors.summarizingDouble(User::getMoney));
           System.out.println("1,获取平均值:"+dou.getAverage());
           System.out.println("2,获取总数据条数:"+dou.getCount());
           System.out.println("3,获取工资总和:"+dou.getSum());
           System.out.println("4,获取工资最大值:"+dou.getMax());
           System.out.println("5,获取工资最大值:"+dou.getMin());
     }

三,元素的分组

方法 描述
groupingBy(Function  classifier) 按照classifier进行分组,单级分组,返回map集合
groupingBy(Function classifier, Collector downstream) 按照classifier进行分组,多级分组,返回Map集合
groupingByConcurrent(Function classifier) 按照classifier进行分组,单级分组,返回ConcurrentMap集合
groupingByConcurrent(Function classifier, Collector downstream) 按照classifier进行分组,多级分组,返回ConcurrentMap集合

例:

public static void main(String[] args) {
             
             List<User> users = Arrays.asList(
                           new User(4,"WQL",23,6091.2),
                           new User(2, "程二", 16,4504.342),
                           new User(3,"张三",29,12010.21),
                           new User(2,"李四",31,8763.21),
                           new User(5,"王五",23,6091.2));
             
             //1,按照ID进行分组(单级分组)
             System.out.println("------------------单级分组------------------");
             Map<Integer,List<User>> map1 = users.stream()
                                                                .collect(Collectors.groupingBy(User::getId));
             System.out.print(map1);
             
             //2,按照ID和年龄进行多级分组
             System.out.println("\n------------------多级分组------------------");
             Map<Integer, Map<Object, List<User>>> map2 =  users.stream().collect(
                                 Collectors.groupingBy(User::getId,
                                               Collectors.groupingBy(e ->  {if(e.getAge()<=30) {
                                                     return "青年";
                                                     }else if(e.getAge()<=50){
                                                            return "中年";}
                                                      else {
                                                            return "老年";}
              })));
             System.out.println(map2);
       }

四,元素的分区

分区和分组功能上差不多,但它没有分组功能强悍,因为传入的函数不同

  • groupingBy分组:传入函数是Function有输出有输入,即能按字段分组也能按条件分组
  • partitioningBy分区:传入函数是Predicate输出为boolean,只能做条件分组
方法 描述
partitioningBy(Predicate predicate) 根据Predicate对输入元素进行 Predicate ,并将它们组织成 Map<Boolean, List<T>>(单级分区)
partitioningBy(Predicate predicate, Collector downstream) 它根据Predicate对输入元素进行 Predicate ,根据另一个 Collector减少每个分区的值,并将其组织成 Map<Boolean, D> ,其值是下游缩减的结果(多级分区)

例:

public static void main(String[] args) {
             
             List<User> users = Arrays.asList(
                           new User(4,"WQL",23,6091.2),
                           new User(2, "程二", 16,4504.342),
                           new User(3,"张三",29,12010.21),
                           new User(2,"李四",31,8763.21),
                           new User(5,"王五",23,6091.2));
             
             //1,按照年龄是否大于30进行分区(单级分组)
             System.out.println("------------------单级分区------------------");
             Map<Boolean,List<User>> map1 =  users.stream().collect(Collectors.partitioningBy((x) -> x.getAge()>30));
             System.out.println(map1);
             
             //2,按照年龄(>30)和工资进行分区(>6000.0)
             System.out.println("------------------多级分组------------------");
             Map<Boolean, Map<Boolean, List<User>>> map2 =  users.stream().collect(Collectors.partitioningBy((x) ->  x.getAge()>30,Collectors.partitioningBy(y -> y.getMoney()>6000.0)));
             System.out.println(map2);
}

五,连接字符串

方法 描述
joining() 按照遇到的顺序将输入元素连接到一个 String中
joining(CharSequence delimiter) 按照遇到的顺序连接由指定的分隔符分隔的输入元素
joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) 按照指定的 Collector分隔的输入元素与指定的前缀和后缀进行连接

例:

public static void main(String[] args) {
         
         List<User> users = Arrays.asList(
                       new User(4,"WQL",23,6091.2),
                       new User(2, "程二", 16,4504.342),
                       new User(3,"张三",29,12010.21),
                       new User(2,"李四",31,8763.21),
                       new User(5,"王五",23,6091.2));
         
         String str = users.stream()
                 .map((x) ->x.getName())
                 .collect(Collectors.joining(",", "--", "--"));
         System.out.print(str);}

结果:--WQL,程二,张三,李四,王五--


路漫漫其修远兮,吾将上下而求索