Java 9 揭秘(18. Streams API 更新)
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)方法允许使用初始种子值创建顺序(可能是无限)流,并迭代应用指定的下一个方法。 当指定的hasNext的predicate 返回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;
}
@Override
public 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 > 1900
Map<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 English
Map<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) 方法允许使用初始种子值创建顺序(可能是无限)流,并迭代应用指定的下一个方法。当指定的hasNext的predicate返回false时,迭代停止。
Collectors类在JDK 9中有两种新方法:filtering()和flatMapping()。 filtering()方法返回在收集元素之前应用过滤器的收集器。如果指定的predicate对于元素返回true,则会收集元素;否则,元素未被收集。 flatMapping()方法返回在收集元素之前应用扁平映射方法的收集器。指定的扁平映射方法被应用到流的每个元素,并且从扁平映射器返回的流的元素的累积。
Java 9 揭秘(18. Streams API 更新)的更多相关文章
- Java 8 中的 Streams API 详解
为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 ...
- Java 8中的 Streams API 详解
为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 ...
- (转)Java 8 中的 Streams API 详解
为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 ...
- Java 9 揭秘全目录汇总
Tips 做一个终身学习的人. 当写这篇文章时,关于Java 9的学习就先告一段落了. 首先介绍一下背景,大概两个月前,我突然有兴趣想看看Java 9,当时读了一本英文原著<Java 9 Rev ...
- 《Java 9 揭秘》全目录汇总
Tips 做一个终身学习的人. 当写这篇文章时,关于Java 9的学习就先告一段落了. 首先介绍一下背景,大概两个月前,我突然有兴趣想看看Java 9,当时读了一本英文原著<Java 9 Rev ...
- Java 8 Streams API 详解
流式编程作为Java 8的亮点之一,是继Java 5之后对集合的再一次升级,可以说Java 8几大特性中,Streams API 是作为Java 函数式的主角来设计的,夸张的说,有了Streams A ...
- Java 9 揭秘(12. Process API 更新)
Tips 做一个终身学习的人. 在本章中,主要介绍以下内容: Process API是什么 如何创建本地进程 如何获取新进程的信息 如何获取当前进程的信息 如何获取所有系统进程的信息 如何设置创建,查 ...
- 深入理解Java 8 Lambda(类库篇——Streams API,Collectors和并行)
转载:http://zh.lucida.me/blog/java-8-lambdas-inside-out-library-features/ 关于 深入理解 Java 8 Lambda(语言篇——l ...
- [转]深入理解Java 8 Lambda(类库篇——Streams API,Collectors和并行)
以下内容转自: 作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-l ...
随机推荐
- solr5Ik分词2
<!--IK分词器--><fieldType name="text_ik" class="solr.TextField"><ana ...
- MYSQL导入数据报错|MYSQL导入超大文件报错|MYSQL导入大数据库报错:2006 - MySQL server has gone away
导SQL数据库结构+数据时,如果数据是批量插入的话会报错:2006 - MySQL server has gone away. 解决办法:找到你的mysql目录下的my.ini配置文件(如果安装目录没 ...
- python制作pdf电子书
python制作pdf电子书 准备 制作电子书使用的是python的pdfkit这个库,pdfkit是 wkhtmltopdf 的Python封装包,因此在安装这个之前要安装wkhtmltopdf 安 ...
- 360安全检测出的WordPress漏洞的修复方法
1.跨站脚本攻击(XSS) 这个漏洞注意是因为用户评论可以提交代码,有安全风险.虽然你的WordPress以及是最新版,但是你的WordPress主题却不一定跟着更新!因此,需要稍微修改一下评论相关的 ...
- 从一道例题谈Arrays.toString()与其他String的转换方法
阅读该篇文章前,请大家事先阅读一下: java.toString(),(String),String.valueOf的区别 有了上述基础后,我接下来谈谈从一道题目中获得的些许收获. 今天在做题是发 ...
- NPOI 生成 excel基本设置
//设置页眉页脚 tempSheet.Header.Center = "2017-04-27"; tempSheet.Footer.Center = "√" + ...
- Codeforces Round #371 (Div. 2) 转换数字
C. Sonya and Queries time limit per test 1 second memory limit per test 256 megabytes input standard ...
- mysql基础之yum安装mysql5.7.18
2017-04-19 一.实验环境 centos7_x64 由于centos7的yum源里默认使用了mariadb替代了mysql,所有我们还得先配置一下yum源.当然mariadb和mysql是兼容 ...
- PhpStorm配置PHP解释器(wampServer版)
PHPStorm(以下简称为PS)和wampServer集成环境安装简单,不再赘述. 本人使用PhpStrom版本为2017.1.4版本. PS刚开始使用会使用自带服务器,但是有几率不能自动匹配到PH ...
- [USACO09OCT]热浪Heat Wave
未经同意,不得转载. The good folks in Texas are having a heatwave this summer. Their Texas Longhorn cows make ...