深入JAVA Stream

之前聊了聊stream的一般性操作,最近看了篇很好的文章,以此基础进一步学习一下Stream。

Stream 简介

Stream是JAVA8引进的对集合(Collection)功能的增强,其借助Lambda表达式极大提高了程序的可读性与并行性,可以对集合对象进行一系列操作。

JAVA Stream与传统的java.io的InputStream没什么联系,也不同于实时处理的Stream,其是对集合功能的增强。

Stream更像是一款高级的Iterator,单向,不可往复,且仅能消费一次。

Stream 数据类型

Stream和集合一样,需要指定流的数据类型。比如一个List 可以转换成一个Stream 。另外Stream为三种基本数据类型提供了专门的类:IntStream,LongStream,DoubleStream。

Stream 操作

操作分类

之前文章介绍了很多stream的方法,这些方法可以分为两种:

  • Intermediate(中间操作):一个流可以跟随多个intermediate操作,其可以执行映射,过滤等对象操作。值得一提的是这些操作都是惰性的,只有在terminal后这些操作才会真正执行。
  • Terminal(终端操作):意味着流的终点,一个流只能有一个treminal操作(你可以使用peek来执行多个clone)。

另外有一种叫做short-circuiting的操作,其可以在无限大的流中抽取有限的或一定时间的stream。

常用的流操作归类为:

  • Intermediate:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

  • Terminal:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

  • Short-circuiting:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

peek

如何将指定的流中间结果打印(这是一个Terminal操作)并继续执行后续步骤呢?即可以使用peek在里面进行终端操作,例如:

  1. stream.peek(e -> System.out.println(e))
  2. .filter(e -> e.length()>2);

Optional

Optional并不是一种操作,而是例如findFirst(),min(),count()等终端操作的返回结果。其使用了类Scala的概念,使用Optional是为了避免值为空所报的异常,使用get()方法获取操作结果,当其为空时get到的就是null而不是异常。其使用格式如下:

  1. Optinal <String> optional = stream.findFirst();
  2. String result = optional.get();

reduce

规约是流式处理的常用方法,其主要将元素按某种形式聚合,如计算的方式如下:

  1. IntStream stream = IntStream.of(1,2,3,4);
  2. System.out.println(stream.reduce((a,b) ->
  3. {
  4. System.out.println("a:"+a+",b:"+b);
  5. return a+b;
  6. }).getAsInt()); //reduce返回一个Optinal对象,需要get一下

控制台输出为:

  1. a:1,b:2
  2. a:3,b:3
  3. a:6,b:4
  4. 10

reduce提供三种重载方式,可以参考这边blog

Match

Stream 有三个 match 方法,从语义上说:

  • allMatch:Stream 中全部元素符合传入的 predicate,返回 true
  • anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
  • noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true

关于predicate,function,Supplier等概念都是属于函数式编程的概念,后续blog可以介绍一下。

Stream的其他使用方法

Stream.generate

Stream.generate() 接受自己实现的 Supplier,通过实现Supplier接口的get方法即可:

  1. private static class EmployeeSupplier implements Supplier<Employee>{
  2. private int index =0;
  3. private Random random = new Random();
  4. @Override
  5. public Employee get() {
  6. return new Employee(""+index++,"test"+index,random.nextInt(70));
  7. }

之后在generate中传入Supplier即可,这里假设生成20个Employee对象并找出其年龄大于20的员工:

  1. Stream.generate(new EmployeeSupplier())
  2. .limit(20)
  3. .filter(employee -> employee.getAge()>20)
  4. .forEach(employee -> System.out.println(employee));

Collectors方法

之前在收集流到数组时经常使用toCollect(Collector.XXX)的形式,其实Collector方法进行归组操作,类似于groupby。

groupingBy

按数据种类归组,例如统计各年龄段的人数:

  1. Map <Integer,List<Person>> group = stream.collect(Collectors.groupingBy(p -> p.getAge()));

partitionBy

接受一个Predict条件,将满足条件和不满足条件进行分组,例如统计成年人与未成年人:

  1. Map <Boolean,List<Person>> partition = stream.collect(Collectors.partitionBy(p -> p.getAge()>18));