Stream的使用

参考资料地址1: java Steam的使用

参考资料地址2: java Steam基础练习

参考资料地址3: Java8的Steam流常用方法和总结

参考资料地址4: 万字详解 Stream 流式编程,写代码也可以很优雅

参考资料地址5: stream流分组

一 Stream简述

Stream是一个接口,该接口表示流。

1.1 获取流的几种方式

  1. 通过Collection集合(单列集合)调用stream()方法获取。【根据单列集合获取】

    Collection中获取流的方法: Stream stream():获取集合对应的流。获取流

  2. 通过Stream中的静态方法根据数组获取流。【根据数组获取】

    在Stream中有一个静态方法,可以根据数组获取流。

    Stream中根据数组获取流的方法: static Stream of(T... values):根据数组或多个元素获取Stream流。

  3. Arrays.stream(T array)

  4. 通过 Stream.builder() 创建: Stream

  5. 通过生成器创建:除了从现有的数据源创建 Stream,我们还可以使用生成器来生成元素。Java 8 中提供了 Stream.generate() 方法和 Stream.iterate() 方法来创建无限 Stream。例如:

    Stream<Integer> stream = Stream.generate(() -> 0); // 创建一个无限流,每个元素都是 0
    Stream<Integer> stream = Stream.iterate(0, n -> n + 1); // 创建一个无限流,从 0 开始递增
    

1.2 Stream中的方法

stream的操作分为两大类,一类为中间操作,一类为终端操作。

中间操作:返回值仍然为一个流,不会消耗流

终端操作:返回最终结果;终端操作会消耗掉流,使之不再可用

1.3 Stream中的注意事项:

  1. 流调用非终结方法返回值都是Stream本身类型,但是返回的并不是自身的对象,返回的结果是一个新的流
  2. 流只能一次性使用,不能多次使用。

二 具体操作

01 遍历操作(forEach和peek) 终

forEach

在Stream中有一个方法叫做forEach,可以对流中的元素进行逐一处理,逐一操作 void forEach(Consumer action):对流中的每一个元素进行逐一操作,逐一处理。参数Consumer表示处理规则。

Consumer是一个函数式接口,这个接口中只有一个抽象方法 void accept(T t):对数据进行操作,进行处理。

forEach方法的参数是Consumer函数式接口,那么可以传递Lambda表达式,这个Lambda表达式表示的是Consumer接口中唯一的一个抽象方法 accept的内容,我们要在Lambda表达式中编写操作规则。]

Arrays.asList("Alice", "Bob", "Charlie").stream().forEach(System.out::println);

peek: peek是一个中间操作方法,它接受一个Consumer函数作为参数,对流中的每个元素执行该函数。与forEach不同的是,peek方法会返回一个新的流,该流中的元素和原始流中的元素相同

/**
     * peek(中间操作) 使用示例
     */
    @Test
    public void testPeek() {
        List<Student> list = Arrays.asList(new Student("张三", 18), new Student("李四", 19));
        //年龄脱敏 并做一些其他处理(略)
        List<Student> resultList = list.stream().peek(s -> s.setAge(null)).collect(Collectors.toList());
        System.out.println(resultList);
        //[StreamTest.Student(name=张三, age=null), StreamTest.Student(name=李四, age=null)]
   }

02 过滤操作 filter

在Stream中有一个方法叫做filter,可以对流中的元素进行过滤筛选。 Stream filter(Predicate predicate):用来对流中的元素进行过滤筛选,返回值是过滤后新的流。参数predicate表示过滤规则。

Predicate是一个函数式接口,里面只有一个抽象方法 boolean test(T t):判断数据是否符合要求。

filter方法参数是Predicate函数式接口,所以可以传递Lambda表达式,该Lambda表达式表示Predicate接口中的唯一的抽象方法 test的内容。我们要在Lambda表达式中编写验证(判断)规则。 如果我们希望某个数据留下,那么就返回true,如果不希望某个数据 留下,那么就返回false。

03 截断 limit

在Stream中有一个方法叫做limit,可以获取流中的前几个元素。 Stream limit(long n):获取流中的前n个元素然后放入到新的流中返回。

04 跳过 skip

在Stream中有一个方法叫做skip,可以跳过流中的前几个元素,获取剩下的元素 Stream skip(long n):跳过流中前n个元素,获取剩下的元素放到一个新的流中返回。

05 去重(Distinct)

去重(Distinct):distinct() 方法用于去除 Stream 中的重复元素,根据元素的 equals()hashCode() 方法来判断是否重复。例如:

java复制代码Stream<Integer> stream = Stream.of(1, 2, 2, 3, 3, 3);
Stream<Integer> distinctStream = stream.distinct(); // 去重

06 合并 concat

在Stream中有一个静态方法叫做concat,可以对两个流进行合并,合并成新的流。 static Stream concat(Stream a, Stream b):对a和b这两个流进行合并,合并成新的流返回。 在Stream中有一个方法就叫做map,可以将流中的元素【映射】到另一个流中。

映射其实指的就是将原来流中的每一个元素都进行某种操作,然后将操作后的元素保存到新的流中。

07 映射 map 终

Stream中的map方法: Stream map(Function mapper):将流中的元素映射到新的流中并返回。参数Function表示映射规则。

Function是一个函数式接口,里面只有一个抽象方法叫做apply R apply(T t):对数据进行处理,然后返回结果。

map方法的参数是Function这个函数式接口,那么我们可以传递Lambda表达式, 这个Lambda表达式表示的Function中唯一的一个抽象方法 map方法的内容,我们在Lambda表达式中编写处理的规则。

示例代码

 public static void main(String[] args) {
        List<Student> list = new ArrayList<Student>();
        list.add(new Student(1, "张三", 18, "北京"));
        list.add(new Student(2, "李四", 18, "北京"));
        list.add(new Student(3, "王五", 18, "南京"));
        list.add(new Student(4, "钱二", 18, "北京"));
        List<Integer> collect = list.stream().map(o -> o.getId()).collect(Collectors.toList());
        collect.forEach(System.out::print);
        //1234

08 扁平映射 flatMap

扁平映射(FlatMap):flatMap() 方法类似于 map() 方法,不同之处在于它可以将每个元素映射为一个流,并将所有流连接成一个流。这主要用于解决嵌套集合的情况

简单来说,flatMap()将集合的集合降维成单个元素的集合

代码示例

         List<Integer> list1 = Arrays.asList(1, 2, 3);
         List<Integer> list2 = Arrays.asList(4, 5, 6);
         List<Integer> list3 = Arrays.asList(7, 8, 3);
         List<List<Integer>> lists = Arrays.asList(list1, list2, list3);
         List<Integer> aggregationList = lists.stream().flatMap(o ->             o.stream()).collect(Collectors.toList());
         System.out.println(aggregationList);//[1, 2, 3, 4, 5, 6, 7, 8, 3]

09 匹配操作 (allMatch、anyMatch 和 noneMatch)

  • anyMatch() :判断条件中,任意一个元素判断为true,则返回true
  • allMatch() : 判断条件中,全部元素判断为true,则返回true
  • noneMatch(): 判断条件中,全部元素判断为flase,则返回true

stream.allMatch() 和 stream.anyMatch()均为终端操作

传入一个Predicate函数式接口,用于指定条件

   @Test
   public void testMatch() {
       List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
       boolean allEven = numbers.stream()
               .allMatch(n -> n % 2 == 0);
       System.out.println(allEven); // 输出结果: false
       boolean hasEven = numbers.stream()
               .anyMatch(n -> n % 2 == 0);
       System.out.println(hasEven); // 输出结果: true
       boolean noneNegative = numbers.stream()
               .noneMatch(n -> n < 0);
       System.out.println(noneNegative); // 输出结果: true
   }

10 统计操作 (count、max 和 min)终

在 Stream API 中,count、max 和 min 是用于统计操作的方法,它们可以用来获取流中元素的数量、最大值和最小值。

  1. count: count 方法用于返回流中元素的数量。它返回一个 long 类型的值,表示流中的元素个数。
  2. max: max 方法用于返回流中的最大值。它返回一个 Optional 对象,如果流为空,则返回一个空的 Optional;如果流非空,则返回流中的最大值的 Optional。
  3. min: min 方法用于返回流中的最小值。它返回一个 Optional 对象,如果流为空,则返回一个空的 Optional;如果流非空,则返回流中的最小值的 Optional。

未传入Comparator则填null,默认用Comparable的compareTo函数比较。

   @Test
   public void testMinMaxCount(){
       List<Integer> list = Arrays.asList(1, 15, 89, null, 2, 56);
       //求最大值,元素为null时会抛出异常
       Optional<Integer> maxOpt = list.stream().filter(Objects::nonNull).max(Integer::compareTo);
       maxOpt.ifPresent(System.out::println);//89
       //求最小值,元素为null时会抛出异常
       Optional<Integer> minOpt = list.stream().filter(Objects::nonNull).min(Integer::compareTo);
       minOpt.ifPresent(System.out::println);//1
       //求元素的个数,元素为null时不计入
       long count = list.stream().filter(Objects::nonNull).count();
       System.out.println("元素个数: "+count);//元素个数: 5     
   }

11 查找操作 (findFirst 和 findAny) 终

findFirst: findFirst 方法用于返回流中的第一个元素。它返回一个 Optional 对象,如果流为空,则返回一个空的 Optional;如果流非空,则返回流中的第一个元素的 Optional。

    @Test
    public void testFindFirst() {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
        Optional<String> first = names.stream().findFirst();
        first.ifPresent(System.out::println); // 输出结果: Alice
    }

findAny: findAny 方法用于返回流中的任意一个元素。它返回一个 Optional 对象,如果流为空,则返回一个空的 Optional;如果流非空,则返回流中的任意一个元素的 Optional。在顺序流中,通常会返回第一个元素;而在并行流中,由于多线程的处理,可能返回不同的元素。

  @Test
    public void testFindAny() {
        // 在顺序流中,通常会返回第一个元素;
        List<Integer> list = Arrays.asList(4, 5, 61, 2, 3);
        for (int i = 0; i < 10; i++) {
            Optional<Integer> any = list.stream().findAny();
            any.ifPresent(System.out::println);//4
        }
        System.out.println("-----------------------");
        // 而在并行流中,由于多线程的处理,可能返回不同的元素。
        for (int i = 0; i < 100; i++) {
            Optional<Integer> any = list.stream().parallel().findAny();
            any.ifPresent(System.out::println);//能返回不同的元素
        }
    }

12 排序 sorted

按照自然顺序或者使用自定义的比较器进行排序。

排序操作的语法如下:

Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)

第一种语法形式中,sorted() 方法会根据元素的自然顺序进行排序。如果元素实现了 Comparable 接口并且具备自然顺序,那么可以直接调用该方法进行排序。

第二种语法形式中,sorted(Comparator<? super T> comparator) 方法接受一个比较器(Comparator)作为参数,用于指定元素的排序规则。通过自定义比较器,可以对非 Comparable 类型的对象进行排序。

   @Test
    public void testSorted() {
        //1按自然顺序排序
        List<Integer> nums = Arrays.asList(4, 5, 61, 2, 3);
        System.out.println(nums.stream().sorted().collect(Collectors.toList()));
        //2 通过自定义比较器排序
        List<Student> list = Arrays.asList(new Student("张三", 18), new Student("李四", 19));
        //stream流倒序排序
        list = list.stream().sorted(Comparator.comparing(Student::getAge).reversed()).collect(Collectors.toList());
        //stream流多条件排序
        list = list.stream().sorted(Comparator.comparing(Student::getAge).thenComparing((Student::getName))).collect(Collectors.toList());
          // s1-s2 升序   s2-s1降序

       /* //list升序排列
        Collections.sort(list, (s1, s2) -> s1.getAge().compareTo(s2.getAge()));
        list.forEach(stu -> System.out.println(stu));*/
    }

13 聚合操作 reduce 终

reduce: reduce是一个终端操作方法,它接受一个BinaryOperator函数作为参数,对流中的元素逐个进行合并操作,最终得到一个结果。该方法会将流中的第一个元素作为初始值,然后将初始值与下一个元素传递给BinaryOperator函数进行计算,得到的结果再与下一个元素进行计算,以此类推,直到遍历完所有元素。

  @Test
    public void testReduce() {
        List<Student> list = Arrays.asList(new Student("张三", 18), new Student("李四", 19));
        //求年龄总和
        Integer sum = list.stream().map(Student::getAge).reduce(0, (Integer::sum));
        //求年龄的最大值,用reduce
        Integer max = list.stream().map(Student::getAge).reduce(Integer.MIN_VALUE, (result, element) -> result > element ? result : element);
        //reduce一个参数的重载形式内部的计算
        list.stream().map(Student::getAge).reduce((result, element) -> result > element ? result : element).ifPresent(System.out::println);
    }

14 收集流中的元素 stream.collect() 终

将流中的元素收集到集合中(将流转成集合)

在Stream中有一个方法叫做collect,可以将流中的元素收集到集合(将流转成集合) R collect(Collector collector):参数collector表示将数据收集到哪种集合。

Collector是一个接口,我们要使用这个接口的实现类对象,这个接口的实现类对象不是由我们去创建的,而是通过 工具类获取,获取Collector的工具类叫做Collectors

Collectors中获取Collector的方法: static Collector toList():通过该方法获取到的Collector对象表示将数据收集到List集合。 static Collector toSet():通过该方法获取到的Collector对象表示将数据收集到Set集合。

收集到数组中

在Stream中有一个方法叫做toArray,可以将流中的内容收集到数组中(将流转成数组) Object[] toArray():将流转成数组

用法一:将流转化为Collection或Map

Collectors.toCollection() 将数据转换成Collection,只要是Collection的实现都可以,例如ArrayList,HashSet,该方法能够接受一个Collection对象

示例:

转化为list or set

 //List
 Stream.of(1,2,3,4,5,6,7,8,9).collect(Collectors.toCollection(ArrayList::new));
 //Set
 Stream.of(1,2,3,4,5,6,7,8,9).collect(Collectors.toCollection(HashSet::new));
 
 // Stream.of(1,2,3,4,5,6,7,8,9).collect(Collectors.toList());
 // Stream.of(1,2,3,4,5,6,7,8,9).collect(Collectors.toSet());

转化为map

/**
 * @author : lyn
 * @date : 2022-05-08 17:09
 */
public class TestStream {
    public static void main(String[] args) {
        List<Student> list = new ArrayList<Student>();
        list.add(new Student(1, "张三", 18, "北京"));
        list.add(new Student(2, "李四", 18, "北京"));
        list.add(new Student(3, "王五", 18, "南京"));
        list.add(new Student(4, "钱二", 18, "北京"));
        //1 将对象list集合转变为,以id为key,自身为value的map集合
        Map<Integer, Student> map = list.stream().collect(Collectors.toMap(Student::getId, Function.identity()));
        for (Map.Entry<Integer, Student> entry : map.entrySet()) {
            System.out.println("key: " + entry.getKey() + ",value: " + entry.getValue());
        }
        //打印结果
        // key: 1,value: Student(id=1, name=张三, age=18, address=北京)
        //key: 2,value: Student(id=2, name=李四, age=18, address=北京)
        //key: 3,value: Student(id=3, name=王五, age=18, address=南京)
        //key: 4,value: Student(id=4, name=钱二, age=18, address=北京)

        //2 如果key重复会抛出java.lang.IllegalStateException: Duplicate key 1异常
        list.add(new Student(2, "钱二", 18, "北京"));
        // 解决方案:多传入一个merge function来处理冲突
        Map<Integer, Student> map2 = list.stream().collect(Collectors.toMap(Student::getId, Function.identity(), (s, o) -> o));
        for (Map.Entry<Integer, Student> entry : map.entrySet()) {
            System.out.println("key: " + entry.getKey() + ",value: " + entry.getValue());
        }
    }
}

用法二:字符串聚合规约

Collectors.joining(),拼接,有三个重载方法,底层实现是StringBuilder,通过append方法拼接到一起,并且可以自定义分隔符(这个感觉还是很有用的,很多时候需要把一个list转成一个String,指定分隔符就可以实现了,非常方便)、前缀、后缀。

  String str = Stream.of(1, 2, 3, 4, 5, 6).map(o -> o.toString()).collect(Collectors.joining(","));
         System.out.println(str);//1,2,3,4,5,6

用法三:统计个数

Collectors.counting() 统计元素个数,这个和Stream.count() 作用都是一样的,返回的类型一个是包装Long,另一个是基本long,但是他们的使用场景还是有区别的,这个后面再提。

 // Long 8
 Stream.of(1,0,-10,9,8,100,200,-80)
                 .collect(Collectors.counting());
 //如果仅仅只是为了统计,那就没必要使用Collectors了,那样更消耗资源
 // long 8
 Stream.of(1,0,-10,9,8,100,200,-80)
                 .count();

用法四:集合分组

Collectos.groupingBy()实现集合分组,返回值为一个Map

stream.forEach()遍历流中的每一个元素,不一定依靠流的顺序,而stream.forEachOrdered()按照流的顺序遍历。

         Student stu1 = new Student("李四",26,"北京");
         Student stu2 = new Student("李文",27,"北京");
         Student stu3 = new Student("赵四",27,"北京");
         Student stu4 = new Student("王五",28,"天津");
         Student stu5 = new Student("张三",26,"呼和浩特");
         Map<String, List<Student>> collect = Arrays.asList(stu1, stu2, stu3, stu4, stu5).
                 stream().collect(Collectors.groupingBy(Student::getAddress));
         collect.entrySet().stream().forEach((entry)->{
             System.out.println(entry.getKey()+"--------------------");
             entry.getValue().forEach(System.out::println);
 
         });

结果

呼和浩特-------------------- Student(name=张三, age=26, address=呼和浩特) 天津-------------------- Student(name=王五, age=28, address=天津) 北京-------------------- Student(name=李四, age=26, address=北京) Student(name=李文, age=27, address=北京) Student(name=赵四, age=27, address=北京)

三 并行流

3.1 什么是并行流

并行流是 Java 8 Stream API 中的一个特性。它可以将一个流的操作在多个线程上并行执行,以提高处理大量数据时的性能。

在传统的顺序流中,所有的操作都是在单个线程上按照顺序执行的。而并行流则会将流的元素分成多个小块,并在多个线程上并行处理这些小块,最后将结果合并起来。这样可以充分利用多核处理器的优势,加快数据处理的速度。

要将一个顺序流转换为并行流,只需调用流的 parallel() 方法即可。示例代码如下所示

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        numbers.stream()
                .parallel()
                .forEach(System.out::println);//顺序不固定

在这个示例中,我们创建了一个包含整数的 List,并通过 stream() 方法将其转换为流。接着调用 parallel() 方法将流转换为并行流,然后使用 forEach 方法遍历流中的元素并输出。

需要注意的是,并行流的使用并不总是适合所有情况。并行流的优势主要体现在数据量较大、处理时间较长的场景下。对于小规模数据和简单的操作,顺序流可能更加高效。在选择使用并行流时,需要根据具体情况进行评估和测试,以确保获得最佳的性能。

此外,还需要注意并行流在某些情况下可能引入线程安全的问题。如果多个线程同时访问共享的可变状态,可能会导致数据竞争和不确定的结果。因此,在处理并行流时,应当避免共享可变状态,或采用适当的同步措施来确保线程安全。

3.2 如何使用并行流提高性能

使用并行流可以通过利用多线程并行处理数据,从而提高程序的执行性能。下面是一些使用并行流提高性能的常见方法:

  1. 创建并行流:要创建一个并行流,只需在普通流上调用 parallel() 方法。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    Stream<Integer> parallelStream = numbers.parallelStream();
    
  2. 利用任务并行性:并行流会将数据分成多个小块,并在多个线程上并行处理这些小块。这样可以充分利用多核处理器的优势。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    numbers.parallelStream()
           .map(n -> compute(n)) // 在多个线程上并行处理计算
           .forEach(System.out::println);
    

    在这个示例中,使用 map 方法对流中的每个元素进行计算。由于并行流的特性,计算操作会在多个线程上并行执行,提高了计算的效率。

  3. 避免共享可变状态:在并行流中,多个线程会同时操作数据。如果共享可变状态(如全局变量)可能导致数据竞争和不确定的结果。因此,避免在并行流中使用共享可变状态,或者采取适当的同步措施来确保线程安全。

  4. 使用合适的操作:一些操作在并行流中的性能表现更好,而另一些操作则可能导致性能下降。一般来说,在并行流中使用基于聚合的操作(如 reducecollect)和无状态转换操作(如 mapfilter)的性能较好,而有状态转换操作(如 sorted)可能会导致性能下降。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    
    // good performance
    int sum = numbers.parallelStream()
                     .reduce(0, Integer::sum);
    
    // good performance
    List<Integer> evenNumbers = numbers.parallelStream()
                                       .filter(n -> n % 2 == 0)
                                       .collect(Collectors.toList());
    
    // potential performance degradation
    List<Integer> sortedNumbers = numbers.parallelStream()
                                         .sorted()
                                         .collect(Collectors.toList());
    

    在这个示例中,reducefilter 的操作在并行流中具有良好的性能,而 sorted 操作可能导致性能下降。

除了上述方法,还应根据具体情况进行评估和测试,并行流是否能够提高性能。有时候,并行流的开销(如线程的创建和销毁、数据切割和合并等)可能超过了其带来的性能提升。因此,在选择使用并行流时,应该根据数据量和操作复杂度等因素进行综合考虑,以确保获得最佳的性能提升。

3.3 并行流的适用场景和注意事项

  1. 大规模数据集:当需要处理大规模数据集时,使用并行流可以充分利用多核处理器的优势,提高程序的执行效率。并行流将数据切分成多个小块,并在多个线程上并行处理这些小块,从而缩短了处理时间。
  2. 复杂的计算操作:对于复杂的计算操作,使用并行流可以加速计算过程。由于并行流能够将计算操作分配到多个线程上并行执行,因此可以有效地利用多核处理器的计算能力,提高计算的速度。
  3. 无状态转换操作:并行流在执行无状态转换操作(如 mapfilter)时表现较好。这类操作不依赖于其他元素的状态,每个元素的处理是相互独立的,可以很容易地进行并行处理。

并行流的注意事项包括:

  1. 线程安全问题:并行流的操作是在多个线程上并行执行的,因此需要注意线程安全问题。如果多个线程同时访问共享的可变状态,可能会导致数据竞争和不确定的结果。在处理并行流时,应避免共享可变状态,或者采用适当的同步措施来确保线程安全。
  2. 性能评估和测试:并行流的性能提升并不总是明显的。在选择使用并行流时,应根据具体情况进行评估和测试,以确保获得最佳的性能提升。有时,并行流的开销(如线程的创建和销毁、数据切割和合并等)可能超过了其带来的性能提升。
  3. 并发操作限制:某些操作在并行流中的性能表现可能较差,或者可能导致结果出现错误。例如,在并行流中使用有状态转换操作(如 sorted)可能导致性能下降或结果出现错误。在使用并行流时,应注意避免这类操作,或者在需要时采取适当的处理措施。
  4. 内存消耗:并行流需要将数据分成多个小块进行并行处理,这可能导致额外的内存消耗。在处理大规模数据集时,应确保系统有足够的内存来支持并行流的执行,以避免内存溢出等问题。

四 其他用例

4.1 Stream将Long类型的数组转成int[]

Long[] longArray = {100L, 200L, 300L};
// 将Long类型的数组转化为Stream对象,然后使用mapToInt方法将每个元素已经映射成对应的int值
int[] intArray = Arrays.stream(longArray).mapToInt(Long::intValue).toArray();

4.2 获取1-5的集合

指定一个常量seed,生成从seed到常量f(由UnaryOperator返回的值得到)的流。

//获取1-5的集合
List<Integer> list = Stream.iterate(1, n -> n+1 ).limit(5).collect(Collectors.toList());

4.3 将以逗号分割的数字字符串转为集合

    /**
     * 将字符串 12,45,89 转成List<Long>
     */
    @Test
    public void testStr2List() {
        String str = "fghj,48,drftyguhji,,";
        List<Long> ids = Stream
                .of(str.split(","))
                .filter(s -> Pattern.matches("^[1-9]\\d*|0$", s))
            	//.filter(NumberUtils::isDigits)//import org.apache.commons.lang3.math.NumberUtils;
                .map(Long::valueOf)
                .collect(Collectors.toList());
        System.out.println(ids);//[48]
    }

4.4 统计各类型的交易金额

  @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class  Item {
        private Integer id;
        private String type;
        private Long money;
    }

    @Test
    public void testCollectMap() {
        //统计各类型的交易金额
        String[] types = {"娱乐", "饮食", "交通"};
        List<Item> baseList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            baseList.add(new Item(i, types[i % 3], 10L));
        }
        baseList.add(new Item(18, types[1], null));
        System.out.println(baseList);
        Map<String, Long> statMap = baseList
                .stream()
                .filter(s -> Objects.nonNull(s.getMoney()))//注意空指针
                .collect(Collectors.toMap(Item::getType, Item::getMoney, Long::sum));
        System.out.println(statMap);//{饮食=30, 娱乐=40, 交通=30}
    }

4.5 lambda 表达式中设置序号

    @Test
    public void testLambdaSetSortNum() {
        //基础数据准备
        List<NumStudent> list = Arrays.asList(new NumStudent(null, "张三", 18), new NumStudent(null, "李四", 19), new NumStudent(null, "李无", 20));
        //需求根据年龄大小排序并设置序号
        //1 利用数组
        //int[] index={1};
        //list.stream().sorted(Comparator.comparing(NumStudent::getAge)).forEach(s -> s.setNum(index[0]++));
        //2 利用AtomicInteger
        AtomicInteger index = new AtomicInteger();
        list.stream().sorted(Comparator.comparing(NumStudent::getAge)).forEach(s -> s.setNum(index.incrementAndGet()));
        System.out.println(list);
        //[StreamTest.NumStudent(num=1, name=张三, age=18), StreamTest.NumStudent(num=2, name=李四, age=19), StreamTest.NumStudent(num=3, name=李无, age=20)]
    }
posted @ 2021-12-18 23:06  进击的小蔡鸟  阅读(1403)  评论(0编辑  收藏  举报