Java8新特性Stream API详解

258 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

Stream API

Stream 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。集合讲的是数据, Stream讲的是计算

前置知识

Lambda表达式:基本使用,四大函数式接口,方法引用
不会的同学可以阅读我的上一篇文章Lambda表达式

特点

  • Stream 不会存储输入的元素,而是计算他们,存储需要特定的API实现
  • Stream 不会改变源对象。而是会返回一个具有结果的新 Stream,意味着可以(xx().xx().xx())
  • Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行(如下,当执行终止操作时才会执行中间操作)。

操作流程

  • 创建Stream 一个数据源(如:集合、数组),获取一个流
  • 中间操作 一个中间操作链,对数据源的数据进行处理
  • 终止操作 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用 image-1654500500775

流的种类

Parallel Stream 并行流

并行流采用多线程Frok/Join框架来实现任务,在这种情况下,不能保证原子性的操作可能会造成与预期外的错误,慎用

Sequential Stream 顺序流(常用)

使用方式

创建Stream
  • 创建Stram方式一:通过集合

    default Stream<E> stream(): 返回一个顺序流
    default Stream<E> parallelStream() 返回一个并行流

          Stream<String> stream = list.stream(); //返回一个顺序流
          Stream<String> parallelStream = list.parallelStream(); //返回一个并行流
    
  • 创建Stream方式二:通过数组

    static <T> Stream<T> stream(T[] array): 返回一个流
    重载形式,能够处理对应基本类型的数组:
    public static IntStream stream(int[] array)
    public static LongStream stream(long[] array)
    public static DoubleStream stream(double[] array)

      	IntStream stream1 = Arrays.stream(arr);
    

    不必去记忆各种流的类名,比如IntStream,LongStream之流,,仅仅需要理解不同的基本类型或引用类型可以创建对应流即可,在IDEA中输入Arrays.stream(arr).var再加上Tab键就能得到如上结果

  • 创建Stream方式三:通过Stream的of()

    public static <T> Stream<T> of(T... values) 返回一个流

      	Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5, 6);
    
  • 创建Stream方式四:创建无限流

    迭代: public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)

            //遍历前10位偶数
            Stream.iterate(0,t->t+2).limit(10).forEach(System.out::println);
    

    生成: public static<T> Stream<T> generate(Supplier<T> s)

            //生成10个随机数
            Stream.generate(Math::random).limit(10).forEach(System.out::println);
    
中间操作

如上所说,中间操作只会等到终止操作执行时再执行,这被称为惰性求值,多个中间操作就组成了一个流,流水线的流

筛选与切片

image-1654504025488

//filter(Predicate p) 查询包含`j`的字符串
        list.stream().filter(s->s.contains("j")).forEach(System.out::println);
        System.out.println();
        
        //limit(n) 只需要前n个全速
        list.stream().limit(3).forEach(System.out::println);
        System.out.println();
        
        //skip(n) 跳过前n个元素 
        list.stream().skip(3).forEach(System.out::println);
        System.out.println();
        
        //distinct 通过hashcode()和equals()去除重复元素
        list.stream().distinct().forEach(System.out::println);
映射

image-1654503996829

    public void t3() {
        //map(Function f) 转为大写字符串
        list.stream().map(s -> s.toUpperCase(Locale.ROOT)).forEach(System.out::println);

        //flatMap() 将流中的每个值都换成另一个流,然后把所有流连接成一个流。
        list.stream().flatMap(this::StringToStream).forEach(System.out::println);
    }

    public Stream<Character> StringToStream(String s) {
        List<Character> characters = new ArrayList<>();
        for (Character c : s.toCharArray()) {
            characters.add(c);
        }
        return characters.stream();
    }
排序

image-1654505319533

        //sorted 自然排序(升序)
        Stream.of(1, 6, 4, 76, 8, 4, 2, 53, 8).sorted().forEach(System.out::println);
        
        //sorted(Comparator com) 定制排序(适用于没有继承Comparable的对象等等)
        Stream.of('a', 'c', 't', 'p').sorted(Character::compare).forEach(System.out::println);
终止操作
  • 终止操作执行之后会生成对应的结果,结果可以是List,Integer或void等,具体看使用的终止操作API

  • 流执行了终止操作后便不可继续使用

匹配与查找

image-1654506826277 image-1654506963938

        int[] arr = new int[]{5, 7, 43, 7, 213, 3523};
        //allMatch 元素是否全大于18
        boolean b = Arrays.stream(arr).allMatch(n -> n > 18);
        //anyMatch 是否有元素大于18
        boolean b2 = Arrays.stream(arr).anyMatch(n -> n > 18);
        //noneMatch 元素是否全小于18
        boolean b3 =Arrays.stream(arr).noneMatch(n -> n > 18);
        //findFirst() 返回第一个元素
        int first = Arrays.stream(arr).findFirst().getAsInt();
        //findAny() 返回任意一个元素与
        int any = Arrays.stream(arr).findAny().getAsInt();
        //count() 返回元素个数
        long count = Arrays.stream(arr).count();
        //max(Comparator com) 返回流中最大值
        int max = Arrays.stream(arr).max().getAsInt();
        //min(Comparator com) 返回流中最小值
        int min = Arrays.stream(arr).min().getAsInt();
        //reduce(T identity, BinaryOperator)——可以将流中元素反复结合起来,得到一个值。返回 T
        int sum = Arrays.stream(arr).reduce(0, Integer::sum);
        System.out.println(sum);
规约

备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名

image-1654507653891

收集

image-1654508113755

Collectors 提供了很多静态方法用于将流收集为List,Set等,同时也可以计算流

image-1654508153989

        Stream.of(1,2,3,4,5,6).collect(Collectors.toList());

Java11好像有一个ListOf(...)的API,这个方法用不到咯

Parallel并行流的注意点示范

比如我们现在有两个ArrayList<Integer>,分别是L1L2,我们想要使用并行流遍历L1的元素并自增放入L2中,于是诞生了以下代码

ArrayList<Integer> L1 = (ArrayList<Integer>) Stream.generate(new Random()::nextInt).limit(1000).collect(Collectors.toList());
PrintStream pw = System.out;
L1.forEach(pw::println);
System.out.println(L1.size());
ArrayList<Integer> L2 = new ArrayList<>();
L1.stream().parallel().map(i->i++).forEach(L2::add);
System.out.println(L2.size());

输出结果如下:

...
276484816
-661750695
472724207
-1944164995
L1 SIZE:1000
L2 SIZE:928

再次运行:

image.png

出现以下问题的原因上面已经说过了,不能保证原子性的方法并行流内就可能会出现预料之外的结果,ArrayList::add不能保证原子性,源码如下

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

ensureCapacityInternal方法计算当前list容量和是否扩容 为什么呢?
比如现在有两个线程A,B ,若同时对L2进行插入操作,同时执行ensureCapacityInternal方法得到结果可以插入,结果A先插入了之后B再插入,容量不够就报以上错误

需要详细了解的同学可以搜索Fork/Join的原理