流的操作类型分为两种:
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 | 通过传递一个预期匹配的对象作为参数并返回一个包含所有匹配到的对象的流。 |
distinct | 返回包含唯一元素的流(唯一性取决于元素相等的实现方式)。 |
limit | 返回一个特定上限的流 |
skip | 返回一个丢弃前n个元素的流 |
List expensiveInvoices = invoices.stream() .filter(inv -> inv.getAmount() > 10_000) .limit(5) .collect(Collectors.toList()); 复制代码
匹配是一个判断是否匹配到给定属性的普遍的数据处理模式,表达式最后返回boolean
方法 | 示意 |
---|---|
allMacth | 流对象每个都要匹配到 |
anyMatch | 流对象有匹配到就中断 |
noneMatch | 流对象无匹配对象 |
boolean expensive = invoices.stream() .allMatch(inv -> inv.getAmount() > 1_000); 复制代码
:流接口还提供了像findFirst和findAny等从流中取出任意的元素。它们能与像filter方法相连接。findFirst和findAny都返回一个可选对象,在串行流中两者都是返回第一个对象,在并行流中findAny返回第一个线程处理最快的数据
方法 | 示意 |
---|---|
findFirst | 返回第一个匹配到的对象 |
findAny | 返回第一个匹配到的对象 |
Optional = invoices.stream() .filter(inv -> inv.getCustomer() == Customer.ORACLE) .findAny(); 复制代码
:流支持映射方法,传递一个函数对象作为方法,把流中的元素转换成另一种类型。这种方法应用于单个元素,将其映射成新元素。
方法 | 示意 |
---|---|
map | 应用于单个元素,将其映射成新元素。 |
List ids = invoices.stream() .map(Invoice::getId) .collect(Collectors.toList()); 复制代码
方法 | 示意 |
---|---|
reduce(BinaryOperator) | 一个函数主要用于累和,求最大值。 |
int product = numbers.stream().reduce(1, (a, b) -> a * b); int max = numbers.stream().reduce(Integer.MIN_VALUE, Integer::max); 复制代码
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); 复制代码
获取最后计算的最小值,最大值和去重复
目前为止你所了解的方法都是返回另一个流或者一个像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)); 复制代码
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) 能即为全部的元素对象。
方法 | 示意 |
---|---|
mapToInt | 函数体返回值为int类型 |
mapToDouble | 函数体返回值为Double类型。 |
mapToLong | 函数体返回值为Long类型。 |
方法 | 示意 |
---|---|
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()); 复制代码
Stream API 支持方便的数据并行。换句话说,你可以明确地让流管道以并行的方式运行而不用关心底层的具体实现。在这背后,Stream API使用了Fork/Join框架充分利用了你机器的多核架构。 你所需要做的无非是用parallelStream()方法替换stream()方法。 不是所有的都使用并行的方式,如下