深入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在里面进行终端操作,例如:
stream.peek(e -> System.out.println(e))
.filter(e -> e.length()>2);
Optional
Optional并不是一种操作,而是例如findFirst(),min(),count()等终端操作的返回结果。其使用了类Scala的概念,使用Optional是为了避免值为空所报的异常,使用get()方法获取操作结果,当其为空时get到的就是null而不是异常。其使用格式如下:
Optinal <String> optional = stream.findFirst();
String result = optional.get();
reduce
规约是流式处理的常用方法,其主要将元素按某种形式聚合,如计算的方式如下:
IntStream stream = IntStream.of(1,2,3,4);
System.out.println(stream.reduce((a,b) ->
{
System.out.println("a:"+a+",b:"+b);
return a+b;
}).getAsInt()); //reduce返回一个Optinal对象,需要get一下
控制台输出为:
a:1,b:2
a:3,b:3
a:6,b: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方法即可:
private static class EmployeeSupplier implements Supplier<Employee>{
private int index =0;
private Random random = new Random();
@Override
public Employee get() {
return new Employee(""+index++,"test"+index,random.nextInt(70));
}
之后在generate中传入Supplier即可,这里假设生成20个Employee对象并找出其年龄大于20的员工:
Stream.generate(new EmployeeSupplier())
.limit(20)
.filter(employee -> employee.getAge()>20)
.forEach(employee -> System.out.println(employee));
Collectors方法
之前在收集流到数组时经常使用toCollect(Collector.XXX)的形式,其实Collector方法进行归组操作,类似于groupby。
groupingBy
按数据种类归组,例如统计各年龄段的人数:
Map <Integer,List<Person>> group = stream.collect(Collectors.groupingBy(p -> p.getAge()));
partitionBy
接受一个Predict条件,将满足条件和不满足条件进行分组,例如统计成年人与未成年人:
Map <Boolean,List<Person>> partition = stream.collect(Collectors.partitionBy(p -> p.getAge()>18));