Java8 Stream详解

383 阅读5分钟

引言: Stream作为java8的特性,基于lambda表达式 ,对集合(包括数组), 功能的增强,它专注于对集合对象的高效遍历聚合操作,提高编程的效率和代码的可读性。

简介

Stream可以看做是一种流(类似于linux终端的 | ),流在管道中传输,并且可以在各个节点上进行各种转换、聚合、过滤、筛选、去重等处理,最后由前面的操作得到最终的处理结果。 Stream产生流常用的方式大致有一下几种,需要详细了解Stream的创建方式可以查看Stream 官方API进行了解:

  1. Steam.of(T... values) 返回一个指定元素T类型的顺序Stream流

  2. Stream.concat(Stream<? extends T> a, Stream<? extends T> b) 返回两个Stream流合并的流,两个流的泛型不同则会被转为Object类型

  3. Collection.Stream() 为集合创建串行流

  4. Collection.parallelStream() 为集合创建并行流(缺省使用ForkJoinPool#commonPool),整个JVM公用一个线程池,很容易导致现成堆积从而产生OOM,使用时最好自定义ForkJoinPool

    public static void main(String[] args) {
     ForkJoinPool forkJoinPool = new ForkJoinPool(10);
    
     List&lt;Goods&gt; goodsList = forkJoinPool.submit(() -&gt; goodsList()
             .parallelStream()
             .filter(goods -&gt; goods.getPrice() &gt;= 25)
             .collect(Collectors.toList())).join();
    
     System.out.println(goodsList);
    }
    

类结构

上图是Stream类的类结构图,里面包含了大部分的中间和终止操作:

  • 中间操作主要有以下方法(此类型方法返回类型都是Stream):map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
  • 终止操作主要有以下方法(此方法类型返回类型都是void):forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

一些例子🌰

为了方便说明,我会创建一个名为Goods的标准bean,使用此bean创建一个List集合, 并且实现Comparable接口(为了代码紧凑, 使用了Lombok进行bean的构建)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Goods implements Comparable&lt;Goods&gt; {
    private int id;
    private String name;
    private int weight;
    private int price;

    @Override
    public int compareTo(Goods goods) {
          // 如果重量相同则根据id进行排序
        if (goods.weight == this.weight) {
            return goods.id - this.id;
        }
        return goods.weight - this.weight;
    }
}

public static List&lt;Goods&gt; goodsList() {
    Goods g1 = new Goods(1, “货物一号”, 100, 20);
    Goods g2 = new Goods(2, “货物二号”, 100, 30);
    Goods g3 = new Goods(3, “货物三号”, 200, 20);
    Goods g4 = new Goods(4, “货物四号”, 400, 25);

    return Lists.newArrayList(g1, g2, g3, g4);
}
  • map(转换)

    public static void main(String[] args) {
      List&lt;String&gt; goodsResult = goodsList().stream()
              .map(goods -&gt; “名称是:” + goods.getName() + “, 价格为: “ + goods.getPrice())
              .collect(Collectors.toList());
      System.out.println(goodsResult);
    }
    

    运行结果:

    map将给定的元素根据表达式进行转换

  • filter(过滤)

    public static void main(String[] args) {
      List&lt;Goods&gt; goodsResult = goodsList().stream()
              .filter(goods -&gt; goods.getWeight() &gt;= 200)
              .collect(Collectors.toList());
      System.out.println(goodsResult);
    }
    

    运行结果:

    filter将给定的元素根据表达式进行过滤,将符合条件表达式的元素保留

  • peek

    public static void main(String[] args) {
      List&lt;Goods&gt; goodsResult = goodsList().stream()
              .peek(goods -&gt; goods.setPrice(1))
              .collect(Collectors.toList());
      System.out.println(goodsResult);
    }
    

    运行结果:

    peek仅执行表达式,对给定元素进行操作,不会对流本身进行变化

  • mapToInt、mapToLong、mapToDouble

    public static void main(String[] args) {
      int goodsResult = goodsList().stream()
              .mapToInt(Goods::getWeight)
              .sum();
      System.out.println(goodsResult);
    }
    

    运行结果:

    mapToInt需要提供一个能够转为Int的Function表达式,将给定元素转化为int值,变为新的IntStream,可以使用新的计算API (mapToLong、mapToDouble同上,不再赘述)

  • distinct(去重)

    public static void main(String[] args) {
      List&lt;Goods&gt; goodsList = goodsList();
      Goods g1 = new Goods(1, “货物一号”, 100, 20);
      goodsList.add(g1);
      List&lt;Goods&gt; goodsResult = goodsList.stream()
              .distinct()
              .collect(Collectors.toList());
      System.out.println(goodsResult);
    }
    

    运行结果:

    distinct对给定元素进行去重,如果是对象会进行equal和hashCode方法进行比较去重

  • sorted(排序) 带参方法

    public static void main(String[] args) {
      List&lt;Goods&gt; goodsResult = goodsList().stream()
              .sorted(Comparator.comparingInt(Goods::getPrice).reversed())
              .collect(Collectors.toList());
      System.out.println(goodsResult);
    }
    

    运行结果:

    无参方法运行结果:

    sorted对给定元素进行排序,如果不传入Comparator对象会对元素进行自然排序,基本类型按照数字大小或者ASCII码进行升序排列,对象如果实现了Comparable接口,则会根据类实现的compareTo方法进行相应的排序

  • limit和skip

    public static void main(String[] args) {
      List&lt;Goods&gt; goodsResult = goodsList().stream()
              .skip(1)
              .limit(2)
              .collect(Collectors.toList());
      System.out.println(goodsResult);
    }
    

    运行结果:

    分别根据传入的值进行元素获取个数的限制和元素的跳过

  • foreach和foreachordered 两者都是对流进行循环遍历,区别在于其前者是对流进行并行处理, 后者严格按照传入流的顺序进行遍历

  • toArray 将流显示的转换为数组,不推荐直接使用,反而不如Collection.toArray本身效率高

  • max和min

    public static void main(String[] args) {
      Optional&lt;Goods&gt; goodsResult = goodsList().stream()
              .max(Comparator.naturalOrder());
      System.out.println(goodsResult.get());
    }
    

    运行结果:

    需要传入一个Comparator对象,获取流中的最大值或最小值,并返回一个Optional对象

  • count 统计流中元素的个数,直接使用等于Collection.size

  • anyMatch、allMatch、noneMatch

    public static void main(String[] args) {
      Boolean goodsResult = goodsList().stream()
              .allMatch(goods -&gt; goods.getWeight() &gt;= 100);
      System.out.println(goodsResult);
    }
    

    运行结果:

    流的元素是否任意、全部、没有一个符合表达式,返回boolean值

  • findFirst和findAny findFirst会获取流中第一个元素,findAny会获取到流中的任意一个匹配元素。两者的区别在于前者一定会获取第一个元素,后者在并行流(parallelStream)中返回元素无法确定

  • flatMap、flatMapInt

    public static void main(String[] args) {
      List&lt;Goods&gt; goodsList1 = goodsList();
      List&lt;Goods&gt; goodsList2 = goodsList();
      List&lt;List&lt;Goods&gt;&gt; goodsAll = Lists.newArrayList(goodsList1, goodsList2);
      List&lt;Goods&gt; goodsList3 = goodsAll.stream()
              .flatMap(List::stream)
              .collect(Collectors.toList());
      System.out.println(goodsList3);
    }
    

    运行结果:

    将流中所有集合(禁止套娃)进行「扁平化」操作,变为一个流,需要提供一个能够转为stream的function