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;
}
@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) 方法允许使用初始种子值创建顺序(可能是无限)流,并迭代应用指定的下一个方法。当指定的hasNextpredicate返回false时,迭代停止。

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

Java 9 揭秘(18. Streams API 更新)的更多相关文章

  1. Java 8 中的 Streams API 详解

    为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 ...

  2. Java 8中的 Streams API 详解

    为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 ...

  3. (转)Java 8 中的 Streams API 详解

    为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 ...

  4. Java 9 揭秘全目录汇总

    Tips 做一个终身学习的人. 当写这篇文章时,关于Java 9的学习就先告一段落了. 首先介绍一下背景,大概两个月前,我突然有兴趣想看看Java 9,当时读了一本英文原著<Java 9 Rev ...

  5. 《Java 9 揭秘》全目录汇总

    Tips 做一个终身学习的人. 当写这篇文章时,关于Java 9的学习就先告一段落了. 首先介绍一下背景,大概两个月前,我突然有兴趣想看看Java 9,当时读了一本英文原著<Java 9 Rev ...

  6. Java 8 Streams API 详解

    流式编程作为Java 8的亮点之一,是继Java 5之后对集合的再一次升级,可以说Java 8几大特性中,Streams API 是作为Java 函数式的主角来设计的,夸张的说,有了Streams A ...

  7. Java 9 揭秘(12. Process API 更新)

    Tips 做一个终身学习的人. 在本章中,主要介绍以下内容: Process API是什么 如何创建本地进程 如何获取新进程的信息 如何获取当前进程的信息 如何获取所有系统进程的信息 如何设置创建,查 ...

  8. 深入理解Java 8 Lambda(类库篇——Streams API,Collectors和并行)

    转载:http://zh.lucida.me/blog/java-8-lambdas-inside-out-library-features/ 关于 深入理解 Java 8 Lambda(语言篇——l ...

  9. [转]深入理解Java 8 Lambda(类库篇——Streams API,Collectors和并行)

    以下内容转自: 作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-l ...

随机推荐

  1. windows手动搭建Kotlin命令行环境

    kotlin官网:https://kotlinlang.org 我们可以从GitHub下载.目前最新版本是1.1.2-5 https://github.com/JetBrains/kotlin/rel ...

  2. android studio IDE 下,设置ACTIVITY全屏

    因为ANDROID STUDIO的JAVA类是继承AppCompatActivity的 ,所以常规的全屏设置并不管用.如果要设置全屏,请参照如下代码/ 1/首先,打开AndroidManifest.x ...

  3. [0] JDK与JRE的区别

    JDK就是Java Development Kit.简单的说JDK是面向开发人员使用的SDK,它提供了Java的开发环境和运行环境.SDK是Software Development Kit 一般指软件 ...

  4. MongoDB-配置翻译

    Configuration File(配置文件) File Format(文件格式) Use the Configuration File(使用配置文件) Core Options(核心设置) sys ...

  5. react-native —— 在Mac上配置React Native Android开发环境排坑总结

    配置React Native Android开发环境总结 1.卸载Android Studio,在终端(terminal)执行以下命令: rm -Rf /Applications/Android\ S ...

  6. 2~62位任意进制转换(c++)

    进制转换的符号表为[0-9a-zA-Z],共61个字符,最大可表示62进制. 思路是原进制先转换为10进制,再转换到目标进制. 疑问: 对于负数,有小伙伴说可以直接将符号丢弃,按照整数进行进位转换,最 ...

  7. oracle表空间增长异常或表空间占用过高问题分析

    本人对oracle调优还处在不断学习状态,这个问题是之前处理的项目上遇到过的,顺利解决了,分享下此类问题的处理思路,不足之处,还请指正. 项目上反馈说业务表空间增长越来越快,上次新增的30G数据文件, ...

  8. 深入浅出TCP/IP协议栈

    TCP/IP协议栈是一系列网络协议的总和,是构成网络通信的核心骨架,它定义了电子设备如何连入因特网,以及数据如何在它们之间进行传输.TCP/IP协议采用4层结构,分别是应用层.传输层.网络层和链路层, ...

  9. 开源搜索引擎abelkhan

    发起一个开源项目http://www.abelkhan.com/ 目前而言,已经用python编写了一个网络爬虫抓取页面,和一个简单的前端 网络爬虫,已经有很多高手写过,我基本上奉行了拿来主义, 得益 ...

  10. jq-animate

    jq-animate: <!doctype html> <html> <head> <meta charset="utf-8"> & ...