java 8 stream

2,840 阅读5分钟

流的操作类型分为两种:

  • Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。 map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

  • Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

  • Short-circuiting:对一个无限及集合返回有限的数据 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

Filter

对象匹配过滤

方法示意
filter通过传递一个预期匹配的对象作为参数并返回一个包含所有匹配到的对象的流。
distinct返回包含唯一元素的流(唯一性取决于元素相等的实现方式)。
limit返回一个特定上限的流
skip返回一个丢弃前n个元素的流
List expensiveInvoices
= invoices.stream()
.filter(inv -> inv.getAmount() > 10_000)
.limit(5)
.collect(Collectors.toList());

Matching:

匹配是一个判断是否匹配到给定属性的普遍的数据处理模式,表达式最后返回boolean

方法示意
allMacth流对象每个都要匹配到
anyMatch流对象有匹配到就中断
noneMatch流对象无匹配对象
boolean expensive =
invoices.stream()
.allMatch(inv -> inv.getAmount() > 1_000);

Finding

:流接口还提供了像findFirst和findAny等从流中取出任意的元素。它们能与像filter方法相连接。findFirst和findAny都返回一个可选对象,在串行流中两者都是返回第一个对象,在并行流中findAny返回第一个线程处理最快的数据

方法示意
findFirst返回第一个匹配到的对象
findAny返回第一个匹配到的对象
Optional =
invoices.stream()
.filter(inv ->
inv.getCustomer() == Customer.ORACLE)
.findAny();

Mapping

:流支持映射方法,传递一个函数对象作为方法,把流中的元素转换成另一种类型。这种方法应用于单个元素,将其映射成新元素。

方法示意
map应用于单个元素,将其映射成新元素。
List ids
= invoices.stream()
.map(Invoice::getId)
.collect(Collectors.toList());

Reducing

方法示意
reduce(BinaryOperator)一个函数主要用于累和,求最大值。
int product = numbers.stream().reduce(1, (a, b) -> a * b);
int max = numbers.stream().reduce(Integer.MIN_VALUE,
Integer::max);

forEach,peek比较

forEach Terminal操作,不再返回stream对象,forEach循环体内不能修改自己包含的本地变量值,也不能用 break/return 之类的关键字提前结束循环。 peek可以对循环对象二次封装并返回新的对象

sorted
它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。

List<Person> persons = new ArrayList();
 for (int i = 1; i <= 5; i++) {
 Person person = new Person(i, "name" + i);
 persons.add(person);
 }
List<Person> personList2 = persons.stream().limit(2).sorted((p1, p2) -> p1.getName().compareTo(p2.getName())).collect(Collectors.toList());
System.out.println(personList2);

min/max/distinct(Terminal)

获取最后计算的最小值,最大值和去重复

Collectors

目前为止你所了解的方法都是返回另一个流或者一个像boolean,int类型的值,或者返回一个可选对象。相比之下,collect方法是一个结束操作,它可以使流里面的所有元素聚集到汇总结果。 传递给collect方法参数是一个java.util.stream.Collector类型的对象。Collector对象实际上定义了一个如何把流中的元素聚集到最终结果的方法。最开始,工厂方法Collectors.toList()被用来返回一个描述了如何把流转变成一个List的Collector对象。后来Collectors类又内建了很多相似的collectors变量。例如,你可以用Collectors.groupingBy方法按消费者把发票分组

Map<Customer, List> customerToInvoices
= invoices.stream().collect(Collectors.group
ingBy(Invoice::getCustomer));

groupingBy/partitioningBy

Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier()).
 limit(100).
 collect(Collectors.groupingBy(Person::getAge));
Iterator it = personGroups.entrySet().iterator();
while (it.hasNext()) {
 Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next();
 System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size());
}

在使用条件“年龄小于 18”进行分组后可以看到,不到 18 岁的未成年人是一组,成年人是另外一组。partitioningBy 其实是一种特殊的 groupingBy,它依照条件测试的是否两种结果来构造返回的数据结构,get(true) 和 get(false) 能即为全部的元素对象。

map 处理的input和output一一对应

方法示意
mapToInt函数体返回值为int类型
mapToDouble函数体返回值为Double类型。
mapToLong函数体返回值为Long类型。

flatMap 处理的input和outPut为一对多关系

方法示意
flatMapToInt函数体返回值为int类型
flatMapDouble函数体返回值为Double类型。
flatMapLong函数体返回值为Long类型。
Stream<List<Integer>> inputStream = Stream.of(
 Arrays.asList(1),
 Arrays.asList(2, 3),
 Arrays.asList(4, 5, 6)
 );
Stream<Integer> outputStream = inputStream.
flatMap((childList) -> childList.stream());

Parallel Streams

Stream API 支持方便的数据并行。换句话说,你可以明确地让流管道以并行的方式运行而不用关心底层的具体实现。在这背后,Stream API使用了Fork/Join框架充分利用了你机器的多核架构。 你所需要做的无非是用parallelStream()方法替换stream()方法。 不是所有的都使用并行的方式,如下

  • parallel streams的内部实现依赖于将数据结构划分成可以让不同线程使用的难易程度。像数组这种数据结构很容易划分,而像链表或者文件这种数据结构很难划分
  • 越是计算流中单个元素花费的资源最高,应用并行越有意义。
  • 如果可能的话尽量用原始数据类型,这样可以占用更少的内存,也更缓存命中率也更高。
  • 流中元素的数据量越大越好,因为并行的成本会分摊到所有元素,并行节省的时间相对会更多。当然,这也跟单个元素计算的成本相关。
  • 一般来说,核越多越好。