参考资料:
http://ju.outofmemory.cn/entry/104364
https://blog.csdn.net/lsmsrc/article/details/41120127
https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/
自从lambda表达式成为Java语言的一部分之后,Java集合(Collections)API就面临着大幅变化。为了不推到重来,所以对现有的API进行改进。
为现有的接口(例如Collection,List和Stream)增加扩展方法;
在类库中增加新的流(stream,即java.util.stream.Stream)抽象以便进行聚集(aggregation)操作;
改造现有的类型使之可以提供流视图(stream view);
改造现有的类型使之可以容易的使用新的编程模式,这样用户就不必抛弃使用以久的类库(并不是说集合API会常驻永存)。
提供更加易用的并行(Parallelism)库。提供显式(explicit)但非侵入(unobstrusive)的并行。
目前的集合库主要依赖于外部迭代:应用程序负责了做什么和怎么做
外部迭代的循环是串行的,且集合框架无法对控制流程进行优化(排序、短路求值等)。
内部迭代,将流程控制权交给类库:应用程序只负责做什么,怎么做交由类库
每个流代表一个值序列,流提供一系列常用的聚集操作,使得我们可以便捷的在它上面进行各种运算。
java 7 实现:发现 type 为 grocery 的所有交易,然后返回以交易值降序排序好的交易 ID 集合 |
---|
List for(Transaction t: transactions){ if(t.getType() == Transaction.GROCERY){ groceryTransactions.add(t); } } Collections.sort(groceryTransactions, new Comparator(){ public int compare(Transaction t1, Transaction t2){ return t2.getValue().compareTo(t1.getValue()); } }); List for(Transaction t: groceryTransactions){ transactionsIds.add(t.getId()); } |
java 8 的实现:发现 type 为 grocery 的所有交易,然后返回以交易值降序排序好的交易 ID 集合 |
---|
List filter(t -> t.getType() == Transaction.GROCERY). sorted(comparing(Transaction::getValue).reversed()). map(Transaction::getId). collect(toList()); |
数据源:任何可以用Iterator描述的对象都可以成为流的数据源,包括Collection类型、BufferedReader、Random、BitSet以及数组(Arrays.stream());流中元素的遍历顺序与数据源的遍历顺序一致。
流的构成:获取一个数据源(source)→ 数据转换(返回新的流)→执行操作获取想要的结果。
流的操作主要分为两类:
中间(Intermediate):一个流可以后面跟随零个或多个 intermediate 操作,每个操作返回一个新的流;这类操作都是惰性化的(lazy),并没有真正开始流的遍历。
map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
终止(Terminal):一个流只能有一个 terminal 操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果。
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
还有一类比较特别的操作Short-circuiting:用于处理操作一个无限大的流。
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
集合主要用来对其元素进行有效(effective)的管理和访问(access),而流并不支持对其元素进行直接操作或直接访问,只是通过声明式操作在其上进行运算然后得到结果。
无存储:流并不存储值;流的元素源自数据源(可能是某个数据结构、生成函数或I/O通道等等),通过一系列计算步骤得到;
天然的函数式风格(Functional in nature):对流的操作会产生一个结果,但流的数据源不会被修改;
惰性求值:多数流操作(包括过滤、映射、排序以及去重)都可以以惰性方式实现。这使得我们可以用一遍遍历完成整个流水线操作,并可以用短路操作提供更高效的实现;
无需上界(Bounds optional):不少问题都可以被表达为无限流(infinite stream):用户不停地读取流直到满意的结果出现为止(比如说,枚举完美数这个操作可以被表达为在所有整数上进行过滤)。集合是有限的,但流不是(操作无限流时我们必需使用短路操作,以确保操作可以在有限时间内完成);
假设我们现在要统计一个List
|
Stream流处理的工作:获取所有人;过滤“男性”;计数。
我们最后调用count()就是获取结果,相当于获取新的List的size。实际上,Stream并没有new一个新List。如果我们不调用count,那么Stream什么都不做。下面这段代码不会打印任何东西:
|
java 8的Stream流的并行处理是以java 7推出的fork/join框架来处理的。
流水线既可以串行执行也可以并行执行,并行或串行是流的属性。
虽然需要显示的指定并行流,但是是非侵入式的(侵入式一般需要实现某个接口,重写某个方法等),不需要开发人员手动实现并行代码。
并行 |
串行 |
---|---|
int sum = shapes.parallelStream() .filter(s -> s.getColor = BLUE) .mapToInt(s -> s.getWeight()) .sum(); |
int sum = shapes.stream() .filter(s -> s.getColor = BLUE) .mapToInt(s -> s.getWeight()) .sum(); |
因为并行的存在,所以stream流的操作应保证无干扰性:
不要干扰数据源(这个条件和遍历集合时所需的条件相似,如果集合在遍历时被修改,绝大多数的集合实现都会抛出ConcurrentModificationException)。
不要干扰其它lambda表达式,当一个lambda在修改某个可变状态而另一个lambda在读取该状态时就会产生这种干扰(所以在lambda表达式只允许使用有效只读的变量,对应用开放,对值封闭)。
遍历所有声明的方法,然后根据方法名称、返回类型以及参数的数量和类型进行匹配。
不使用Stream流 |
---|
for (Method method : enclosingInfo.getEnclosingClass().getDeclaredMethods()) { if (method.getName().equals(enclosingInfo.getName())) { Class< ? >[] candidateParamClasses = method.getParameterTypes(); if (candidateParamClasses.length == parameterClasses.length) { boolean matches = true; for (int i = 0; i < candidateParamClasses.length; i += 1) { if (!candidateParamClasses[i].equals(parameterClasses[i])) { matches = false; break; } } if (matches) { // finally, check return type if (method.getReturnType().equals(returnType)) { return method; } } } } } throw new InternalError("Enclosing method not found"); |
使用Stream流 |
---|
return Arrays.stream(enclosingInfo.getEnclosingClass().getDeclaredMethods()) .filter(m -> Objects.equal(m.getName(), enclosingInfo.getName())) .filter(m -> Arrays.equal(m.getParameterTypes(), parameterClasses)) .filter(m -> Objects.equals(m.getReturnType(), returnType)) .findFirst() .orElseThrow(() -> new InternalError("Enclosing method not found")); |
比较可以发现,Stream流不仅消除了所有的临时变量,而且代码更加紧凑,可读性更好,也不容易出错。
假设我们需要得到一个按名字排序的专辑列表,专辑列表里面的每张专辑都至少包含一首四星及四星以上的音轨,为了构建这个专辑列表,我们可以这么写:
不使用Stream流:
使用Stream流:
|
流通过.collect()方法返回一个集合,该方法接收一个类型为Collector的参数。
Collectors提供了大量常用的工厂方法,根据不同的入参返回不同的实现,有 toList()、toSet()、toMap(K,V)等,其中toMap方法还可以指定容器和解决key冲突方式。
groupingBy(Classify),还可以指定Map容器和收集器类型
Map> favsByArtist =
tracks.stream()
.filter(t -> t.rating >= 4)
.collect(Collectors.groupingBy(t -> t.artist));
Map> favsByArtist =
tracks.stream()
.filter(t -> t.rating >= 4)
.collect(Collectors.groupingBy(t -> t.artist,
Collectors.toSet()));
以java 7引入的Fork/Join模型为基础实现Stream流的并行计算
Fork/Join的实现原理如下,为了能够并行化任意流上的所有操作,我们把流抽象为Spliterator(支持顺序依次访问数据,也支持分解数据):
把问题分解为子问题;
串行解决子问题从而得到部分结果(partial result);
合并部分结果合为最终结果。
Spliterator与Iterator的用法区别在于,如果开发者还知道源的其他元数据时(数据大小),类库就可以通过Spliterator提供一个更加高效的实现(就像JDK中所有的集合一样)。
Stream.iterate(0,n->n+3);
ArrayList arrayList = new ArrayList();
Spliterator spliterator = arrayList.spliterator();
Iterator iterator = arrayList.iterator();
boolean parallel = true;
StreamSupport.stream(spliterator,parallel);
StreamSupport.stream(Spliterators.spliterator(iterator,arrayList.size(),Spliterator.DISTINCT),parallel);
Comparator接口中新增了若干用于生成比较器的实用方法
静态方法Comparator.comparing()接收一个函数,返回一个Comparator。这种方式不仅简洁了代码(无需匿名类),而且便于进行各种组合操作。
//比较器反序
people.sort(comparing(p -> p.getLastName()).reversed());
//多条件排序
Comparator c = Comparator.comparing(p -> p.getLastName())
.thenComparing(p -> p.getFirstName());
people.sort(c);