Tips
做一个终身学习的人。

在本章中,主要介绍以下内容:

  • Stream接口中添加了更加便利的方法来处理流
  • Collectors类中添加了新的收集器(collectors)

JDK 9中,在Streams API中添加了一些便利的方法,根据类型主要添加在:

  • Stream接口
  • Collectors

Stream接口中的方法定义了新的流操作,而Collectors类中的方法定义了新的收集器。

本章的源代码位于名为com.jdojo.streams的模块中,其声明如下所示。

// module-info.java
module com.jdojo.streams {exports com.jdojo.streams;
}

一. 新的流操作

在JDK 9中,Stream接口具有以下新方法:

default Stream<T> dropWhile(Predicate<? super T> predicate)
default Stream<T> takeWhile(Predicate<? super T> predicate)
static <T> Stream<T> ofNullable(T t)
static <T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)

在JDK 8中,Stream接口有两种方法:skip(long count)limit(long count)skip()方法从头开始跳过指定的数量元素后返回流的元素。 limit()方法从流的开始返回等于或小于指定数量的元素。第一个方法从一开始就删除元素,另一个从头开始删除剩余的元素。两者都基于元素的数量。 dropWhile()takeWhile()相应地分别与skip()limit()方法很像;然而,新方法适用于Predicate而不是元素的数量。

可以将这些方法想象是具有异常的filter()方法。 filter()方法评估所有元素上的predicate,而dropWhile()takeWhile()方法则从流的起始处对元素进行predicate评估,直到predicate失败。

对于有序流,dropWhile()方法返回流的元素,从指定predicate为true的起始处丢弃元素。考虑以下有序的整数流:

1, 2, 3, 4, 5, 6, 7

如果在dropWhile()方法中使用一个predicate,该方法对小于5的整数返回true,则该方法将删除前四个元素并返回其余部分:

5, 6, 7

对于无序流,dropWhile()方法的行为是非确定性的。 它可以选择删除匹配predicate的任何元素子集。 当前的实现从匹配元素开始丢弃匹配元素,直到找到不匹配的元素。

dropWhile()方法有两种极端情况。 如果第一个元素与predicate不匹配,则该方法返回原始流。 如果所有元素与predicate匹配,则该方法返回一个空流。

takeWhile()方法的工作方式与dropWhile()方法相同,只不过它从流的起始处返回匹配的元素,而丢弃其余的。

Tips
使用dropWhile()takeWhile()方法处理有序和并行流时要非常小心,因为可能对性能有影响。 在有序的并行流中,元素必须是有序的,在这些方法返回之前从所有线程返回。 这些方法处理顺序流效果最佳。

如果元素为非空,则Nullable(T t)方法返回包含指定元素的单个元素的流。 如果指定的元素为空,则返回一个空的流。 在流处理中使用flatMap()方法时,此方法非常有用。 考虑以下map ,其值可能为null:

Map<Integer, String> map = new HashMap<>();
map.put(1, "One");
map.put(2, "Two");
map.put(3, null);
map.put(4, "four");

如何在此map中获取一组排除null的值? 也就是说,如何从这map中获得一个包含“One”,“Two”和“Four”的集合? 以下是JDK 8中的内容:

// In JDK 8
Set<String> nonNullvalues = map.entrySet().stream()          .flatMap(e ->  e.getValue() == null ? Stream.empty() : Stream.of(e.getValue())).collect(toSet());

注意在flatMap()方法中的Lambda表达式内使用三元运算符。 可以使用ofNullable()方法在JDK 9中使此表达式更简单:

// In JDK 9
Set<String> nonNullvalues = map.entrySet().stream()          .flatMap(e ->  Stream.ofNullable(e.getValue())).collect(toSet());

新的iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)方法允许使用初始种子值创建顺序(可能是无限)流,并迭代应用指定的下一个方法。 当指定的hasNextpredicate返回false时,迭代停止。 调用此方法与使用for循环相同:

for (T n = seed; hasNext.test(n); n = next.apply(n)) {// n is the element added to the stream
}

以下代码片段会生成包含1到10之间的所有整数的流:

Stream.iterate(1, n -> n <= 10, n -> n + 1)

下面包含一个完整的程序,演示如何在Stream接口中使用新的方法。

// StreamTest.java
package com.jdojo.streams;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import java.util.stream.Stream;
public class StreamTest {public static void main(String[] args) {System.out.println("Using Stream.dropWhile() and Stream.takeWhile():");testDropWhileAndTakeWhile();System.out.println("\nUsing Stream.ofNullable():");testOfNullable();System.out.println("\nUsing Stream.iterator():");testIterator();}public static void testDropWhileAndTakeWhile() {List<Integer> list = List.of(1, 3, 5, 4, 6, 7, 8, 9);System.out.println("Original Stream: " + list);List<Integer> list2 = list.stream().dropWhile(n -> n % 2 == 1).collect(toList());System.out.println("After using dropWhile(n -> n % 2 == 1): " + list2);List<Integer> list3 = list.stream().takeWhile(n -> n % 2 == 1).collect(toList());System.out.println("After using takeWhile(n -> n % 2 == 1): " + list3);}public static void testOfNullable() {Map<Integer, String> map = new HashMap<>();map.put(1, "One");map.put(2, "Two");map.put(3, null);map.put(4, "Four");Set<String> nonNullValues = map.entrySet().stream()          .flatMap(e ->  Stream.ofNullable(e.getValue())).collect(toSet());        System.out.println("Map: " + map);System.out.println("Non-null Values in Map: " + nonNullValues);}public static void testIterator() {        List<Integer> list = Stream.iterate(1, n -> n <= 10, n -> n + 1).collect(toList());System.out.println("Integers from 1 to 10: " + list);}
}

输出结果为:

Using Stream.dropWhile() and Stream.takeWhile():
Original Stream: [1, 3, 5, 4, 6, 7, 8, 9]
After using dropWhile(n -> n % 2 == 1): [4, 6, 7, 8, 9]
After using takeWhile(n -> n % 2 == 1): [1, 3, 5]
Using Stream.ofNullable():
Map: {1=One, 2=Two, 3=null, 4=Four}
Non-null Values in Map: [One, Four, Two]
Using Stream.iterator():
Integers from 1 to 10: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

二. 新的收集器

Collectors类有以下两个返回Collector新的静态方法:

<T,A,R> Collector<T,?,R> filtering(Predicate<? super T> predicate, Collector<? super T,A,R> downstream)
<T,U,A,R> Collector<T,?,R> flatMapping(Function<? super T,? extends Stream<? extends U>> mapper, Collector<? super U,A,R> downstream)

filtering()方法返回在收集元素之前应用过滤器的收集器。 如果指定的predicate对于元素返回true,则会收集元素; 否则,元素未被收集。

flatMapping()方法返回在收集元素之前应用扁平映射方法的收集器。 指定的扁平映射方法被应用到流的每个元素,并且从扁平映射器(flat mapper)返回的流的元素的累积。

这两种方法都会返回一个最为有用的收集器,这种收集器用于多级别的递减,例如downstream处理分组(groupingBy )或分区(partitioningBy)。

下面使用Employee类来演示这些方法的使用。

// Employee.java
package com.jdojo.streams;
import java.util.List;
public class Employee {private String name;private String department;private double salary;private List<String> spokenLanguages;public Employee(String name, String department, double salary,List<String> spokenLanguages) {this.name = name;this.department = department;this.salary = salary;this.spokenLanguages = spokenLanguages;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getDepartment() {return department;}public void setDepartment(String department) {this.department = department;}public double getSalary() {return salary;}public void setSalary(double salary) {this.salary = salary;}public List<String> getSpokenLanguages() {return spokenLanguages;}public void setSpokenLanguages(List<String> spokenLanguages) {this.spokenLanguages = spokenLanguages;}@Overridepublic String toString() {return "[" + name + ", " + department + ", " + salary + ", " + spokenLanguages +"]";}public static List<Employee> employees() {return List.of(new Employee("John", "Sales", 1000.89, List.of("English", "French")),new Employee("Wally", "Sales", 900.89, List.of("Spanish", "Wu")),new Employee("Ken", "Sales", 1900.00, List.of("English", "French")),new Employee("Li", "HR", 1950.89, List.of("Wu", "Lao")),new Employee("Manuel", "IT", 2001.99, List.of("English", "German")),new Employee("Tony", "IT", 1700.89, List.of("English")));}
}

一个员工具有姓名,部门,工资以及他或她所说的语言等属性。 toString()方法返回一个表示所有这些属性的字符串。 static employees()方法返回员工们的列表,如表下所示。

Name Department Salary Spoken Languages
John Sales 1000.89 English, French
Wally Sales 900.89 Spanish, Wu
Ken Sales 1900.00 English, French
Li HR 1950.89 Wu, Lao
Manuel IT 2001.99 English, German
Tony IT 1700.89 English

可以按照以下方式获取按部门分组的员工列表:

Map<String,List<Employee>> empGroupedByDept = Employee.employees().stream().collect(groupingBy(Employee::getDepartment, toList()));
System.out.println(empGroupedByDept);

输出结果为:

{Sales=[[John, Sales, 1000.89, [English, French]], [Wally, Sales, 900.89, [Spanish, Wu]], [Ken, Sales, 1900.0, [English, French]]], HR=[[Li, HR, 1950.89, [Wu, Lao]]], IT=[[Manuel, IT, 2001.99, [English, German]], [Tony, IT, 1700.89, [English]]]}

此功能自JDK 8以来一直在Streams API中。现在,假设想获取按部门分组的员工列表,员工的工资必须大于1900才能包含在列表中。 第一个尝试是使用过滤器,如下所示:

Map<String, List<Employee>> empSalaryGt1900GroupedByDept = Employee.employees().stream().filter(e -> e.getSalary() > 1900).collect(groupingBy(Employee::getDepartment, toList()));
System.out.println(empSalaryGt1900GroupedByDept);

输出结果为:

{HR=[[Li, HR, 1950.89, [Wu, Lao]]], IT=[[Manuel, IT, 2001.99, [English, German]]]}

从某种意义上说,已经达到了目标。 但是,结果不包括任何员工工资没有大于1900的部门。这是因为在开始收集结果之前过滤了所有这些部门。 可以使用新的filtering()方法返回的收集器来实现此目的。 这个时候,如果收入1900以上的部门没有员工,该部门将被列入最终结果,并附上一份空的员工列表。

Map<String, List<Employee>> empGroupedByDeptWithSalaryGt1900 = Employee.employees().stream().collect(groupingBy(Employee::getDepartment,filtering(e -> e.getSalary() > 1900.00, toList())));
System.out.println(empGroupedByDeptWithSalaryGt1900);

输出结果为:

{Sales=[], HR=[[Li, HR, 1950.89, [Wu, Lao]]], IT=[[Manuel, IT, 2001.99, [English, German]]]}

这一次,结果包含Sales部门,即使没有此部门有没有工资在1900以上的员工。

让我们尝试一下按部门分组的员工所说的语言属性的集合。 以下代码片段尝试使用Collectors类的mapping()方法返回的Collector

Map<String,Set<List<String>>> langByDept = Employee.employees().stream().collect(groupingBy(Employee::getDepartment,mapping(Employee::getSpokenLanguages, toSet())));
System.out.println(langByDept);

输出的结果为:

{Sales=[[English, French], [Spanish, Wu]], HR=[[Wu, Lao]], IT=[[English, German], [English]]}

如输出所示,使用mapping()方法接收到的是Set<List<String>>而不是Set<String>。 在将字符串收集到一个集合中之前,需要对List <String>进行扁平化以获取字符串流。 使用新的flatMapping()方法返回的收集器来做这项任务:

Map<String,Set<String>> langByDept2 = Employee.employees().stream().collect(groupingBy(Employee::getDepartment,flatMapping(e -> e.getSpokenLanguages().stream(), toSet())));
System.out.println(langByDept2);

输出结果为:

{Sales=[English, French, Spanish, Wu], HR=[Lao, Wu], IT=[English, German]}

这次得到了正确的结果。 下面包含一个完整的程序,演示如何在收集数据时使用过滤和扁平映射(flat mapping)。

// StreamCollectorsTest.java
package com.jdojo. streams;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static java.util.stream.Collectors.filtering;
import static java.util.stream.Collectors.flatMapping;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
public class StreamCollectorsTest {public static void main(String[] args) {System.out.println("Testing Collectors.filtering():");testFiltering();System.out.println("\nTesting Collectors.flatMapping():");testFlatMapping();}public static void testFiltering() {Map<String, List<Employee>> empGroupedByDept = Employee.employees().stream().collect(groupingBy(Employee::getDepartment, toList()));                System.out.println("Employees grouped by department:");System.out.println(empGroupedByDept);// Employees having salary > 1900 grouped by department:Map<String, List<Employee>> empSalaryGt1900GroupedByDept = Employee.employees().stream().filter(e -> e.getSalary() > 1900).collect(groupingBy(Employee::getDepartment, toList()));                System.out.println("\nEmployees having salary > 1900 grouped by department:");System.out.println(empSalaryGt1900GroupedByDept);// Group employees by department who have salary > 1900Map<String, List<Employee>> empGroupedByDeptWithSalaryGt1900 = Employee.employees().stream().collect(groupingBy(Employee::getDepartment,filtering(e -> e.getSalary() > 1900.00, toList())));                System.out.println("\nEmployees grouped by department having salary > 1900:");System.out.println(empGroupedByDeptWithSalaryGt1900);// Group employees by department who speak at least 2 languages// and 1 of them is EnglishMap<String, List<Employee>> empByDeptWith2LangWithEn = Employee.employees().stream().collect(groupingBy(Employee::getDepartment,filtering(e -> e.getSpokenLanguages().size() >= 2&&e.getSpokenLanguages().contains("English"),toList())));                        System.out.println("\nEmployees grouped by department speaking min. 2" +" languages of which one is English:");System.out.println(empByDeptWith2LangWithEn);}public static void testFlatMapping(){Map<String,Set<List<String>>> langByDept = Employee.employees().stream().collect(groupingBy(Employee::getDepartment,mapping(Employee::getSpokenLanguages, toSet())));                System.out.println("Languages spoken by department using mapping():");System.out.println(langByDept);Map<String,Set<String>> langByDept2 = Employee.employees().stream().collect(groupingBy(Employee::getDepartment,flatMapping(e -> e.getSpokenLanguages().stream(), toSet())));  System.out.println("\nLanguages spoken by department using flapMapping():");System.out.println(langByDept2) ;      }
}

输出的结果为:

Testing Collectors.filtering():
Employees grouped by department:
{Sales=[[John, Sales, 1000.89, [English, French]], [Wally, Sales, 900.89, [Spanish, Wu]], [Ken, Sales, 1900.0, [English, French]]], HR=[[Li, HR, 1950.89, [Wu, Lao]]], IT=[[Manuel, IT, 2001.99, [English, German]], [Tony, IT, 1700.89, [English]]]}
Employees having salary > 1900 grouped by department:
{HR=[[Li, HR, 1950.89, [Wu, Lao]]], IT=[[Manuel, IT, 2001.99, [English, German]]]}
Employees grouped by department having salary > 1900:
{Sales=[], HR=[[Li, HR, 1950.89, [Wu, Lao]]], IT=[[Manuel, IT, 2001.99, [English, German]]]}
Employees grouped by department speaking min. 2 languages of which one is English:
{Sales=[[John, Sales, 1000.89, [English, French]], [Ken, Sales, 1900.0, [English, French]]], HR=[], IT=[[Manuel, IT, 2001.99, [English, German]]]}
Testing Collectors.flatMapping():
Languages spoken by department using mapping():
{Sales=[[English, French], [Spanish, Wu]], HR=[[Wu, Lao]], IT=[[English, German], [English]]}
Languages spoken by department using flapMapping():
{Sales=[English, French, Spanish, Wu], HR=[Lao, Wu], IT=[English, German]}

三. 总结

JDK 9向Streams API添加了一些便利的方法,使流处理更容易,并使用收集器编写复杂的查询。

Stream接口有四种新方法:dropWhile()),takeWhile()ofNullable()iterate()。对于有序流,dropWhile()方法返回流的元素,从指定predicate为true的起始处丢弃元素。对于无序流,dropWhile()方法的行为是非确定性的。它可以选择删除匹配predicate的任何元素子集。当前的实现从匹配元素开始丢弃匹配元素,直到找到不匹配的元素。 takeWhile()方法的工作方式与dropWhile()方法相同,只不过它从流的起始处返回匹配的元素,而丢弃其余的。如果元素为非空,则Nullable(T t)方法返回包含指定元素的单个元素的流。如果指定的元素为空,则返回一个空的流。新的iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)方法允许使用初始种子值创建顺序(可能是无限)流,并迭代应用指定的下一个方法。当指定的hasNextpredicate返回false时,迭代停止。

Collectors类在JDK 9中有两种新方法:filtering()flatMapping()filtering()方法返回在收集元素之前应用过滤器的收集器。如果指定的predicate对于元素返回true,则会收集元素;否则,元素未被收集。 flatMapping()方法返回在收集元素之前应用扁平映射方法的收集器。指定的扁平映射方法被应用到流的每个元素,并且从扁平映射器返回的流的元素的累积。

Java 9 揭秘(18. Streams API 更新)相关推荐

  1. Java 8 中的 Streams API 详解

    Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利.高效的聚合操作,或者大批量数据操作 .Stream API 借助于同样新出现的 L ...

  2. java.awt.api_Java SE 11(18.9)中的API更新

    java.awt.api Java SE 11也被命名为18.9(基于使用发布年份和月份的新命名方案),预计将在9月的最后一周发布GA. 频繁发布新JDK版本的新方法是允许语言创建者引入新功能,并向开 ...

  3. Java SE 11(18.9)中的API更新

    Java SE 11也被命名为18.9(基于使用发布年份和月份的新命名方案),预计将在9月的最后一周发布GA. 频繁发布新JDK版本的新方法是允许语言创建者引入新功能,并向开发人员社区更快地进行API ...

  4. java8 streams_当Java 8 Streams API不够用时

    java8 streams Java 8与往常一样是妥协和向后兼容的版本. JSR-335专家组可能尚未与某些读者就某些功能的范围或可行性达成一致的版本 . 请参阅Brian Goetz关于为什么-的 ...

  5. 当Java 8 Streams API不够用时

    Java 8与往常一样是妥协和向后兼容的版本. JSR-335专家组可能无法与某些读者就某些功能的范围或可行性达成一致的发行版. 请参阅Brian Goetz关于为什么-的一些具体解释. -Java ...

  6. java streams_使用JShell的Java 9 Streams API

    java streams 这篇文章着眼于使用JShell的Java 9 Streams API. Streams API的更改以Java 8中Streams的成功为基础,并引入了许多实用程序方法– t ...

  7. 使用JShell的Java 9 Streams API

    这篇文章着眼于使用JShell的Java 9 Streams API. Streams API的更改以Java 8中Streams的成功为基础,并引入了许多实用程序方法– takeWhile,drop ...

  8. Java 8 Streams API:对流进行分组和分区

    这篇文章展示了如何使用Streams API中可用的Collectors将具有groupingBy的流元素和具有partitioningBy的流元素进行groupingBy . 考虑一系列Employ ...

  9. Java SE 8新功能介绍:使用Streams API处理集合

    使用Java SE 8 Streams的代码更干净,易读且功能强大..... 在" Java SE 8新功能介绍"系列的这篇文章中,我们将深入解释和探索代码,以了解如何使用流遍历集 ...

最新文章

  1. 公开课-C++学习路线实战导引:从0开始到操作系统内核开发
  2. Python CNN风格迁移
  3. 【go网络编程】-RPC编程
  4. ubuntu下vscode调试开发踩过的坑
  5. cmos门电路输入端悬空相当于_TTL和CMOS,CMOS电平和TTL电平区别
  6. C++官方文档-this
  7. cbc系统是指_制动EBD,CBC是什么意思?
  8. 京东方明年iPhone OLED面板供应量是今年3倍 份额占比约为20%
  9. asp.net试题(三)
  10. 红帽子服务器怎么重装系统,红帽子RHCS套件安装与配置
  11. SetDll把Dll文件注入到.exe应用程序中
  12. ros 发布信息频率_ROS入门笔记二基础
  13. python各个版本区别_Python 的各个版本
  14. Ubuntu无盘工作站安装详细步骤
  15. vue3实现动态组件加载写法
  16. ftp文件上传及下载工具类
  17. 基于安卓WebServicw天气预报demo
  18. idea打不开eclipse项目的问题
  19. IOC和DI浅显易懂的理解
  20. 【linux系统】ubuntu18.04蓝牙耳机没有声音

热门文章

  1. sap模块介绍_SAP系统如何快速上手?
  2. Rem布局的原理解析
  3. Whoops, looks like something went wrong.
  4. java中BigDicemal的运算
  5. centos ping不通内网 网关 外网 域名等以解决 通过设置为动态IP(启用dhcp协议)
  6. Oracle用rowid删除同一张表的重复记录
  7. libvirt热迁移报Connection refused错误解决办法
  8. mwget安装及使用
  9. AD转换中参考电压的作用 .
  10. Windows系统中常见的进程DOS操作命令