这些你必须要会Stream操作

306 阅读11分钟

我正在参加「掘金·启航计划」

4月份刚换了份工作, 之前在的公司要求不能用户lamdba表达式和streamAPI, 说是阅读性不好,以后不好维护 (也是无语的~ ~ ~)

新公司到没有什么要求, 主要做亚马逊云,很多的业务都是用Node.js写的, 很多函数操作, 让我用JAVA重构某些业务时,确实挺懵的,所以回头来撸一下Stream

内容比较简单, 主要也是想自己复习复习, 求轻喷~ ~ ~!05B064DA.png

1. Stream概述

Java8中有两大最为重要的特性。

1)Lambda 表达式 (这个我就不说了, 各位都是大佬,应该都是手到擒来的)

2)Stream API (java.util.stream.*包下)

说到Stream便容易想到I/O Stream,而实际上我们这里讲的Stream它是Java8中对数据处理的一种抽象描述; 我们可以把它理解为数据的管道,我们可以通过这条管道提供给我们的API很方便的对里面的数据进行复杂的操作!比如查找、过滤和映射(类似于使用SQL);更厉害的是可以使用Stream API 来并行执行操作;

简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式,解决了已有集合类库操作上的弊端。

总结:Stream是操作数据的一套工具,封装了对数据的各种操作:比如查找、过滤和映射(解决了原有的集合类的弊端)

2. 传统集合操作vs Stream API操作

2.1 传统方式遍历集合

几乎所有的集合(如Collection 接口或Map 接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元 素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。例如:

  List<String> list = new ArrayList<>();
        list.add("马云");
        list.add("马化腾");
        list.add("李彦宏");
        list.add("雷军");
        list.add("刘强东");
        for (String name : list) {
            System.out.println(name);
        }

遍历一个集合里面的元素很简单,那遍历集合中的元素是否符合某个条件呢? 那是不是我们在遍历集合的时候又要加上一个if判断? 这种写法想想就觉得麻烦!

比如:

需求1:找出姓马的大佬 需求2:再从姓马的中找到名字长度等于3的

传统是写法:

        /*接上面的list集合*/
        // 需求1:找出姓马的大佬
        List<String> newList = new ArrayList<>();
        for (String s : list) {
            if (s.startsWith("马")) {
                newList.add(s);
            }
        }
        for (String name : newList) {
            System.out.println(name);
        }
​
        System.out.println("----------------");
        // 需求2:再从姓马的中找到名字长度等于3的
        List<String> newList1 = new ArrayList<>();
        for (String s : newList) {
            if (s.trim().length()==3) {
                newList1.add(s);
            }
        }
        for (String name : newList1) {
            System.out.println(name);
        }

每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。那Stream能给我们带来怎样更加优雅的写法呢?

Stream写法:

​
          // 需求1:找出姓马的大佬
        list.stream().filter(x->x.startsWith("马")).forEach(System.out::println);
​
​
        System.out.println("---------------------------");
        // 需求2:再从姓马的中找到名字长度等于3的
        list.stream().filter(x->x.startsWith("马"))
                     .filter(x->x.length()==3)
                     .forEach(System.out::println);

3. Stream流理解

那么,流到底是什么呢?简短的定义就是“从支持数据处理操作的源生成的元素序列”。

●元素序列

——就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元素(如ArrayList 与 LinkedList)。但流的目的在于表达计算,比如filter、sorted和map。集合讲的是数据(存储),流讲的是计算(处理)。

●源

——流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。

●数据处理操作

——流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如filter、map、reduce、find、match、sort等。流操作可以顺序执行,也可并行执行。

4. Stream流的创建

创建一个流非常简单,有以下几种常用的方式:

1)Collection的默认方法stream()和parallelStream()

stream()和parallelStream()都是将集合转换为流,parallelStream 是并行流,Stream 是串行流

2)Arrays.stream()

3)Stream.of()

4)Stream.iterate()

5)Stream.generate()

见如下:

public static void main(String[] args) {
​
        // stream方法和parallelStream方法的区别
        List<Integer> list = Arrays.asList( 4, 3, 2, 4);
        list.stream().forEach(System.out::println);
        System.out.println("----------------------");
​
                 list.parallelStream().forEach(System.out::println);
        System.out.println("----------------------");
​
        // Arrays.Stream()
        Stream<Integer> arrayOfstream = Arrays.stream(new Integer[]{1, 2, 3, 4});
        arrayOfstream.forEach(System.out::println);
        System.out.println("----------------------");
​
​
        Stream<String> integerStream = Stream.of("振兴","中国","未来");
        integerStream.forEach(System.out::println);
        System.out.println("----------------------"); 
    }

  Stream.iterate()迭代无限流 (  第一个参数:表示从哪开始,第二个参数:表示以递增2的方式,产生无限流)

       Stream.iterate(1, new UnaryOperator<Integer>() {
            @Override
            public Integer apply(Integer integer) {
                return integer + 2;
            }
        }).limit(10).forEach(System.out::println);
​
        // 使用lambda 表达式改造
        Stream.iterate(10,x->x+2).limit(10).forEach(System.out::println);
        System.out.println("----------------------");

注意: 注意: UnaryOperator继承了Function<T,T>接口 ,表示传入的参数类型和返回的参数类型是同一类型 UnaryOperator接口的 功能就是:对数据进行操作,生成一个与同类型对象

limit(10) 表示 终止无限流,只保留10个元素

Stream.generate() 生成无限流 Math::random 表示随机产生 0-1 之间的随机数

       Stream.generate(new Supplier<Double>() {
            @Override
            public Double get() {
                return Math.random();
            }
        }).limit(10).forEach(System.out::println);
​
        // lambda表达式改造
        Stream.generate(()-> Math.random()).limit(10).forEach(System.out::println);
        // new Random().nextInt(10) 表示随机产生 1-10 之间的随机数
​
        Stream.generate(()->new Random().nextInt(10)).limit(10).forEach(System.out::println);

5. Stream 提供的API

5.1 筛选和切片

filter(Predicate<T> p):过滤(根据传入的Lambda返回的ture/false 从流中过滤掉某些数据(筛选出某些数据))

distinct():去重(根据流中数据的 hashCode和 equals去除重复元素)

limit(long n):限定保留n个数据

skip(long n):跳过n个数据

图片4.png

// 需求:对制定的集合进行过滤出偶数并去重复并保留其中三个并跳过指定1个后进行打印输出
public static void main(String[] args) { 
     
        List<Integer> list = Arrays.asList(1, 2, 1, 3, 3, 2, 4,6,8);
     
        list.stream()
           .filter(new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer%2==0;
                }
             }) // 对数据过滤出偶数
          .distinct() // 去重
          .limit(3) // 保留3个
          .skip(1) // 跳过一个
          .forEach(System.out::println);// 输出
    
        // lambda 改造
       list.stream()
                .filter(s->s%2==0)// 对数据过滤出偶数
                .distinct() // 去重
                .limit(3) // 保留3个
                .skip(1) // 跳过一个
                .forEach(System.out::println);// 输出
      
    }

5.2 映射

map(Function<T, R> f):接收一个函数作为参数,该函数会被应用到流中的每个元素上,并将其映射成一个新的元素。

flatMap(Function<T, Stream<R>> mapper):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

图片7.png

需求1:把给定的集合中的数据都变成大写 (如上图)

需求2: 把给定的两个集合进行合并(如上图)

//  需求1:把给定的集合中的数据都变成大写
public static void main(String[] args) {
        List<String> list = Arrays.asList("i", "love", "money"); 
        list.stream().map(new Function<String, String>() {
            @Override
            public String apply(String s) {
                return s.toUpperCase();
            }
        }).forEach(System.out::println);
    
     // lambda 改造 
     list.stream().map(s->s.toUpperCase()).forEach(System.out::println);
  }
 // 需求2:    把给定的两个集合进行合并
 public static void main(String[] args) {
        List<Integer> list1 = Arrays.asList(1, 2, 3, 1);
        List<Integer> list2 = Arrays.asList(3, 1, 4, 5);
        // 1.把两个集合存入stream流中
        Stream<List<Integer>> listStream = Stream.of(list1, list2);
       // 2. 对上述Stream流中的集合数据进行流的转换
        listStream.flatMap(new Function<List<Integer>, Stream<Integer>>() {
            @Override
            public Stream<Integer> apply(List<Integer> integers) {
                return integers.stream();
            }
        }).forEach(System.out::println);
     
        // lambda 改造 
        Stream.of(list1, list2).flatMap(l->l.stream()).distinct().forEach(System.out::println);
    }

5.3 排序

sorted():自然排序使用Comparable的int compareTo(T o)方法

sorted(Comparator com):定制排序使用Comparator的int compare(T o1, T o2)方法

需求:给指定的集合( List list = Arrays.asList(1, 2, 1, 3, 3, 2, 4);)进行升序和降序排序

 public static void main(String[] args) {
​
        //1. 指定一个集合
        List<Integer> list = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
        //2. 自然排序 (升序)
        list.stream().sorted().forEach(System.out::println);
        System.out.println("----------------------------------");
        //3. 指定排序规则(降序)
        // 方式一
        list.stream().sorted(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        }).forEach(System.out::println);
     
        // lambda 改造
        list.stream().sorted((o1,o2)->o2.compareTo(o1)).forEach(System.out::println);
    }

5.4 查找匹配

allMatch:检查是否匹配所有元素

anyMatch:检查是否至少匹配一个元素

noneMatch:检查是否没有匹配的元素

findFirst:返回第一个元素(返回值为Optional<T>)

findAny:返回当前流中的任意元素(一般用于并行流)

Optional是Java8新加入的一个容器,这个容器只存1个或0个元素,它用于防止出现NullpointException,它提供如下方法:

•isPresent()  

判断容器中是否有值。

•T get()

获取容器中的元素,若容器为空则抛出NoSuchElement异常。*

需求1: 检查是否每一个元素都是大于0

需求2: 检查是否存在一个元素是小于0

需求3: 检查是否真的没有匹配到为0的元素

需求4: 返回集合中的第一个元素

需求5: 返回集合中的任意一个元素

//需求1: 检查是否每一个元素都是大于0
public static void main(String[] args) { 
        List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4); 
       // allMatch:检查是否匹配所有元素
       boolean allMatch = list.stream().allMatch(new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer > 0;
            }
        });
       // lambda 改造
       boolean allMatch = list.stream().allMatch(x -> x > 0);
      System.out.println("检查是否每一个元素都是大于0的:" + allMatch);
}
     //需求2: 检查是否存在一个元素是小于0
     public static void main(String[] args) { 
      List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);  
       //anyMatch:检查是否至少匹配一个元素
        list.stream().anyMatch(new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer<0;
            }
        });
        // lambda 改造
        boolean anyMatch = list.stream().anyMatch(x -> x < 0);
        System.out.println("检查是否存在一个元素是小于0的:" + anyMatch);
    }
​
 //需求3: 检查是否真的没有匹配到为0的元素
 public static void main(String[] args) { 
      List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);  
      // noneMatch:检查是否没有匹配的元素
      list.stream().noneMatch(new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer==0;
            }
        });
        // lambda 改造
        boolean noneMatch = list.stream().noneMatch(x -> x == 0);
        System.out.println("检查是否真的没有匹配到为0的元素:" + noneMatch);
        }
        
 //需求4: 返回集合中的第一个元素
 public static void main(String[] args) { 
      List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);         
       // findFirst:返回第一个元素(返回值为Optional<T>)
        Optional<Integer> first = list.stream().findFirst();
        if (first.isPresent()) { // 判断是否存在数据
            System.out.println("返回第一个元素:" + first.get());
        }
    }
        
 //需求5: 返回集合中的任意一个元素
 public static void main(String[] args) { 
      List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);         
      // findAny:返回当前流中的任意元素(一般用于并行流)
        Optional<Integer> anyEle = list.stream().findAny();
        System.out.println(anyEle.get());
   }

5.5 统计

    count():返回流中元素的总个数

    max(Comparator<T>):返回流中最大值

   min(Comparator<T>):返回流中最小值

需求1: 获取给定一个集合数据的总数

需求2: 获取给定一个集合中的最大值

需求3: 获取给定一个集合中的最小值

//需求1:  获取给定一个集合数据的总数
public static void main(String[] args) { 
        List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);
//        count():返回流中元素的总个数
        long count = list.stream().count();
        System.out.println("流中的元素的总数:" + count);
}
//需求2: 获取给定一个集合中的最大值
public static void main(String[] args) { 
        List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);
       //  max(Comparator<T>):返回流中最大值
    
      // 方案一: 通过在max方法中new Comparator 匿名对象
     Optional<Integer> max = list.stream().max(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1.compareTo(o2);
            }
        });
     //lambda 改造
    Optional<Integer> max = list.stream().max((o1, o2) -> o1.compareTo(o2)); 
    
    // 方法引用改造
    Optional<Integer> min1 = list.stream().max(Integer::compareTo);
    
     Optional<Integer> max = list.stream().max(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1-o2;
            }
        });
      //lambda 改造
    Optional<Integer> max = list.stream().max((o1, o2) -> o1-o2); 
    // 推荐使用这个
    Optional<Integer> max = list.stream().max(Comparator.naturalOrder());
    
    // 方案二:使用Comparator
    list.stream().max(Comparator.comparing(new Function<Integer, Integer>() {
            @Override
            public Integer apply(Integer integer) {
                return integer.intValue();
            }
        }));
      // lambda表达式 改造
    list.stream().max(Comparator.comparing(i->i.intValue()));
     // 方法引用改造 
    Optional<Integer> max = list.stream().max(Comparator.comparing(Integer::intValue));
   
    
    System.out.println("流中的元素的最大值:" + max.get());
}
​
注意: 排序必须是自然循序(升序)
//需求3: 获取给定一个集合中的最小值
public static void main(String[] args) { 
        List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);
       //  min(Comparator<T>):返回流中最小值
       // 演化过程和求最大值是一样的,这里不再演示
       Optional<Integer> min1 = list.stream().min(Integer::compareTo);
        Optional<Integer> max = list.stream().min(Comparator.naturalOrder());
        Optional<Integer> min = list.stream().min(Comparator.comparing(Integer::intValue));
        System.out.println("流中的元素的最小值:" + min.get());
}
​

5.6 汇总

collect()横空出世! collect(Collector<T, A, R>):将流转换为其他形式。

需求:

collect:将流转换为其他形式:list

collect:将流转换为其他形式:set

collect:将流转换为其他形式:TreeSet

collect:将流转换为其他形式:map

collect:将流转换为其他形式:sum

collect:将流转换为其他形式:avg

collect:将流转换为其他形式:max

collect:将流转换为其他形式:min

  public static void main(String[] args) {
        List<Integer> streamList = Arrays.asList(1, 2, 1, 3, -1, 2, 4);
​
//        collect:将流转换为其他形式:list
        List<Integer> integerList = streamList.stream().collect(Collectors.toList());
​
//        collect:将流转换为其他形式:set
        Set<Integer> integerSet = streamList.stream().collect(Collectors.toSet());
      
​
//        collect:将流转换为其他形式:TreeSet
      list.stream().collect(Collectors.toCollection(new Supplier<Collection<Integer>>() {
            @Override
            public Collection<Integer> get() {
                return new TreeSet<>();
            }
        }));
      
       // lambda 改造
        TreeSet<Integer> integerTreeSet = streamList.stream().collect(Collectors.toCollection(()-> new TreeSet<>()));
      // 方法引用改造
       list.stream().collect(Collectors.toCollection(TreeSet::new));
        
        
//        collect:将流转换为其他形式:map (不能去掉重复的数据)
      
       Map<Integer, Integer> collect = list.stream().distinct().collect(Collectors.toMap(
           
           new Function<Integer, Integer>() {
                     @Override
                public Integer apply(Integer integer) {
                    return integer;
                }
            }, 
           new Function<Integer, Integer>() {
                     @Override
                public Integer apply(Integer integer) {
                    return integer;
                }
            }
       ));
       
      // lambda 改造
        Map<Integer, Integer> collect = streamList.stream().distinct().collect(Collectors.toMap(k -> k, v -> v));
        
        collect.forEach((k,v)->System.out.println(k + ":" + v));
​
​
//        collect:将流转换为其他形式:sum
      
       Integer sum = list.stream().collect(Collectors.summingInt(new ToIntFunction<Integer>() {
            @Override
            public int applyAsInt(Integer value) {
                return value.intValue();
            }
        }));
      
      // lambda 改造
       Integer sum = list.stream().collect(Collectors.summingInt(v->v.intValue()));
      // 方法引用改造 
       Integer sum =  list.stream().collect(Collectors.summingInt(Integer::intValue));
        System.out.println("集合中的数据之和:" + sum );
​
//        collect:将流转换为其他形式:avg 
      
       // 演化过程和 求和一样,这里不再演示
        Double avg = streamList.stream().collect(Collectors.averagingDouble(Integer::doubleValue));
        System.out.println("集合中的数据之平均值:"+avg);
​
//        collect:将流转换为其他形式:max
      // 演化过程和 求和一样,这里不再演示
        Optional<Integer> maxInteger = list.stream().collect(Collectors.maxBy(Integer::compareTo));
        System.out.println("最大值:" + maxInteger.get());
​
//        collect:将流转换为其他形式:min
      // 演化过程和 求和一样,这里不再演示
        Optional<Integer> minInteger = list.stream().collect(Collectors.minBy(Integer::compareTo));
        System.out.println("最小值:" + minInteger.get());
  }

5.7 分组和分区

Collectors.groupingBy()对元素做group操作。分组--根据条件分成多个组   Collectors.partitioningBy()对元素进行二分区操作。分区--根据boolean条件分成两个区

需求1: 根据商品分类名称进行分组

需求2: 根据商品价格是否大于1000进行分区

 private static List<Product> products = new ArrayList<>();
​
​
     static {
        products.add(new Product(1L, "苹果手机", 8888.88,"手机"));
        products.add(new Product(2L, "华为手机", 6666.66,"手机"));
        products.add(new Product(3L, "联想笔记本", 7777.77,"电脑"));
        products.add(new Product(4L, "机械键盘", 999.99,"键盘"));
        products.add(new Product(5L, "雷蛇鼠标", 222.22,"鼠标"));
    }
​
​
    public static void main(String[] args) {
​
      //需求1:  根据商品分类名称进行分组
      Map<String, List<Product>> collect = products.stream().collect(Collectors.groupingBy(new Function<Product, String>() {
            @Override
            public String apply(Product product) {
                return product.getBrand();
            }
        }));
        
        // lambda 改造
         Map<String, List<Product>> collect = products.stream().collect(Collectors.groupingBy(p->p.getBrand()));
        
        // 方法引用改造
        Map<String, List<Product>> stringListMap = products.stream().collect(Collectors.groupingBy(Product::getBrand)); 
        
        System.out.println("-----------------------------");
        
     //需求2:  根据商品价格是否大于1000进行分区
        
        Map<Boolean, List<Product>> collect1 = products.stream().collect(Collectors.partitioningBy(new Predicate<Product>() {
            @Override
            public boolean test(Product product) {
                return product.getPrice() > 1000;
            }
        }));
        // lambda改造
        Map<Boolean, List<Product>> collect = products.stream().collect(Collectors.partitioningBy(p -> p.getPrice() > 1000));
        System.out.println(collect);
​
​
    }
运行结果:
​
      分组结果:
       /**
         * collect = {
         * 电脑=[Product{id=3, name='联想笔记本', price=7777.77, brand='电脑'}],
         * 手机=[Product{id=1, name='苹果手机', price=8888.88, brand='手机'},
         *      Product{id=2, name='华为手机', price=6666.66, brand='手机'}],
         *
         * 鼠标=[Product{id=5, name='雷蛇鼠标', price=222.22, brand='鼠标'}],
         * 键盘=[Product{id=4, name='机械键盘', price=999.99, brand='键盘'}]}
         */
    
       分区结果:  
         /**
         * collect1 = {
         * false=[Product{id=4, name='机械键盘', price=999.99, brand='键盘'},
         *        Product{id=5, name='雷蛇鼠标', price=222.22, brand='鼠标'}],
         *
         * true=[Product{id=1, name='苹果手机', price=8888.88, brand='手机'},
         *       Product{id=2, name='华为手机', price=6666.66, brand='手机'},
         *       Product{id=3, name='联想笔记本', price=7777.77, brand='电脑'}]}
         */
         
         

\