• java中stream常用api介绍


    stream相关api对于集合操作非常方便,可以通过链式编程完成数据的处理,有时候很复杂的数据处理通过使用stream的相关api可以非常方便的完成,使用lambda表达式更可以简化为一行代码,下面就介绍一下stream的相关api方法,感受一下如何使用。

    一、构建stream操作流

    构建stream流的方式有很多种,比较常见的两种方式:
    一是通过集合的 .stream() 构建流或 .parallelStream() 构建并行流;二是通过Stream类的相关api构建流。

    1. 通过集合的stream()或parallelStream()方法构建:
    List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    Set<Integer> set = new HashSet<>(list);
    
    // 构建普通流
    Stream<Integer> stream1 = list.stream();
    Stream<Integer> stream2 = set.stream();
    
    // 构建并行流
    Stream<Integer> pstream1 = list.parallelStream();
    Stream<Integer> pstream2 = set.parallelStream();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这里的并行流要注意,它默认使用的是系统中的 ForkJoinPool.commonPool 线程池,这个线程池默认大小是CPU核数,如果要调整线程池大小,可以有两种方法调整系统变量:

    System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "4");
    或
    -Djava.util.concurrent.ForkJoinPool.common.parallelism=4
    
    • 1
    • 2
    • 3

    添加一个打印逻辑验证处理线程名:

    pstream1.forEach(item -> System.out.println(Thread.currentThread().getName() + " : " + item));
    
    • 1

    没有添加上面的参数打印输出内容是:

    main : 7
    main : 6
    ForkJoinPool.commonPool-worker-18 : 2
    ForkJoinPool.commonPool-worker-4 : 1
    ForkJoinPool.commonPool-worker-18 : 10
    main : 9
    ForkJoinPool.commonPool-worker-4 : 4
    ForkJoinPool.commonPool-worker-25 : 3
    ForkJoinPool.commonPool-worker-11 : 8
    ForkJoinPool.commonPool-worker-29 : 5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    添加配置后的输出内容:

    main : 7
    main : 6
    ForkJoinPool.commonPool-worker-1 : 3
    ForkJoinPool.commonPool-worker-1 : 5
    ForkJoinPool.commonPool-worker-2 : 9
    ForkJoinPool.commonPool-worker-3 : 2
    ForkJoinPool.commonPool-worker-1 : 4
    ForkJoinPool.commonPool-worker-3 : 1
    ForkJoinPool.commonPool-worker-0 : 8
    ForkJoinPool.commonPool-worker-2 : 10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    可见配置参数确实调整了系统线程池的大小,由于主线程也会参与计算,所以对于计算密集型的服务,线程池一般设置为CPU核数 - 1。

    1. 通过Stream的api构建
      通过stream的api构建,主要是Stream.of()方法构建:
    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
    stream.forEach(System.out::println);
    
    • 1
    • 2

    对于特定类型的stream还有专门的api构建,比如IntStream的range()和rangeClosed()方法构建一个范围内的数据,其中range()方法包含开始数据不包含结束数据,rangeClosed()方法包含开始数据同时包含结束数据。

    // 包括开始不包括结束
    System.out.println("----------------stream1----------------");
    IntStream stream1 = IntStream.range(1, 10);
    stream1.forEach(System.out::println);
    
    // 包括开始和结束
    System.out.println("----------------stream2----------------");
    IntStream stream2 = IntStream.rangeClosed(1, 10);
    stream2.forEach(System.out::println);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 通过Arrays的stream()方法构建:
    int[] arr = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    IntStream stream = Arrays.stream(arr);
    stream.forEach(System.out::println);
    
    • 1
    • 2
    • 3

    上面就是几种常用的构建stream流方式,使用stream流主要是处理数据,处理数据又分为中间方法和终结方法,它们的主要区别就是中间方法处理完后还可以继续使用stream流进行接下来的处理;而终结方法执行后的流不能继续使用,要想继续使用就必须重新开启一个流再次执行。
    下面所有的流使用的都是这个stream做演示:

    List<Integer> list = new ArrayList<>(Arrays.asList(3, 6, 10, 5, 5, 2, 6, 4, 9, 7, 8, 1, 8, 9));
    Stream<Integer> stream = list.stream();
    
    • 1
    • 2
    二、中间方法

    中间方法执行后获取到的仍然是一个可以使用的流,常用的中间方法如下:

    1. 过滤数据filter()
    // 筛选大于3且小于8的数据
    stream.filter(new Predicate<Integer>() {
        @Override
        public boolean test(Integer i) {
            return i > 3 && i < 8;
        }
    }).forEach(item -> System.out.println(item));
    
    // lambda表达式
    stream.filter(i -> i > 3 && i < 8).forEach(System.out::println);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 选择一个区间的数据:skip()是跳过的数据条数,limit()是返回的数据条数
    // 跳过前2条数据返回5条数据
    stream.skip(2).limit(5).forEach(System.out::println);
    
    • 1
    • 2
    1. 数据去重distinct()
    stream.distinct().forEach(System.out::println);
    
    • 1
    1. 数据排序sorted()
    // 默认排序是升序排列
    stream.sorted().forEach(System.out::println);
    
    // 自定义排序:降序排列
    stream.sorted(new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1 > o2 ? -1 : (o1.intValue() == o2.intValue() ? 0 : 1);
        }
    }).forEach(item -> System.out.println(item));
    
    // lambda表达式
    stream.sorted((o1, o2) -> o1 > o2 ? -1 : (o1.intValue() == o2.intValue() ? 0 : 1)).forEach(System.out::println);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. 修改数据类型map()
    stream.map(new Function<Integer, String>() {
        @Override
        public String apply(Integer i) {
            return "i-" + i;
        }
    }).forEach(item -> System.out.println(item));
    
    // lambda表达式
    stream.map(i -> "i-" + i).forEach(System.out::println);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 修改数据类型flatMap()
    stream.flatMap((Function<Integer, Stream<?>>) num -> {
        // 因式分解该整数
        if(num <= 2) {
            return Stream.of(num);
        }
    
        List<Integer> nums = new ArrayList<>();
        int i = num;
        while (i >= 2) {
            for(int j = 2; j <= i; j++) {
                if(i % j == 0) {
                    nums.add(j);
                    i /= j;
                    break;
                }
            }
        }
        return nums.stream();
    }).forEach(System.out::println);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    注意:map()和flatMap()这两个方法都能实现改变数据类型,主要区别是map()方法修改类型后返回的是单个元素、flatMap()方法修改类型后返回的是一个stream流,主要用于修改类型后返回的是一个集合的场景。
    7. 获取数据peek()方法
    在数据流处理过程中,我们想查看一下当前数据的内容或记录数据信息到日志中,可以通过peek()方法执行,该方法只是对数据查看而不会改变数据的内容:

    // 打印数据
    stream.peek(System.out::println).forEach(System.out::println);
    
    • 1
    • 2

    peek()方法与forEach()方法的区别是:peek()输出数据后不影响流的继续操作;而forEach()方法输出数据后流不能继续执行。peek()方法执行虽然不能修改元素,但是可以修改元素内属性的值而对流的执行不产生影响。

    @Data
    @Builder
    public class DemoVo {
    
        private int id;
        private String name;
    }
    
    List<DemoVo> list = Arrays.asList(DemoVo.builder().build(), DemoVo.builder().build(), DemoVo.builder().build());
    // 通过peek()方法设置对象的属性值
    list.stream().peek(System.out::println).peek(c -> c.setName("name")).forEach(System.out::println);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    三、终结方法

    终结方法执行完后,这个流不能继续执行,常见的终结方法有下面这些:

    1. max()、min()、count()三个取值方法
    // 最大值
    Integer max = stream.max(Comparator.comparingInt(o -> o)).get();
    System.out.println(max);
    
    // 最小值
    Integer min = stream.min(Comparator.comparingInt(o -> o)).get();
    System.out.println(min);
    
    // 计数
    long count = stream.count();
    System.out.println(count);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. forEach() 遍历流中的数据
    stream.forEach(System.out::println);
    
    • 1
    1. toArray()方法将流转换为数组
    Integer[] arr = stream.toArray(new IntFunction<Integer[]>() {
        @Override
        public Integer[] apply(int value) {
            return new Integer[value];
        }
    });
    
    // lambda表达式
    Integer[] arr = stream.toArray(value -> new Integer[value]);
    System.out.println(Arrays.toString(arr));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. collect()将流收集到指定类型集合中
    // 收集到list
    List<Integer> list = stream.collect(Collectors.toList());
    
    // 收集到set
    Set<Integer> set = stream.collect(Collectors.toSet());
    
    // 收集到map:两个函数分别用于键的生成规则和值的生成规则,要注意键不能重复,否则会抛出异常
    Map<Integer, String> map = stream.collect(Collectors.toMap(new Function<Integer, Integer>() {
        @Override
        public Integer apply(Integer i) {
            return i;
        }
    }, new Function<Integer, String>() {
        @Override
        public String apply(Integer i) {
            return "val-" + i;
        }
    }));
    
    // lambda表达式
    Map<Integer, String> map = stream.collect(Collectors.toMap(i -> i, i -> "val-" + i));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
  • 相关阅读:
    基于JavaWeb的网站图片爬虫系统
    代码重构:面向单元测试
    使用vscode操作本地git
    基于Linux安装Hive
    面试常用排序查找算法
    2023年中国智慧医院市场现状及行业发展趋势分析[图]
    Apache Flink 1.12.0 on Yarn(3.1.1) 所遇到的問題
    C和指针 第13章 高级指针话题 13.2 高级声明
    YbtOJ「数据结构」第4章 线段树
    前端项目使用指定字体样式
  • 原文地址:https://blog.csdn.net/dream_xin2013/article/details/134524472