Streams支持大量不同的操作。我们已经了解了最重要的操作,如filtermap。发现所有其他可用的操作(参见Stream Javadoc)。我们深入研究更复杂的操作collectflatMapreduce

本节中的大多数代码示例使用以下人员列表进行演示:

class Person {String name;int age;Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return name;}
}List<Person> persons =Arrays.asList(new Person("Max", 18),new Person("Peter", 23),new Person("Pamela", 23),new Person("David", 12));
复制代码

Collect


Collect是一个非常有用的终端操作,以流的元素转变成一种不同的结果,例如一个List,Set或Map。Collect接受Collector包含四种不同操作的操作:供应商,累加器,组合器和修整器。这听起来非常复杂,但是Java 8通过Collectors类支持各种内置收集器。因此,对于最常见的操作,您不必自己实现收集器。

让我们从一个非常常见的用例开始:

List<Person> filtered =persons.stream().filter(p -> p.name.startsWith("P")).collect(Collectors.toList());System.out.println(filtered);
复制代码

代码输出:

 [Peter, Pamela]
复制代码

正如您所看到的,流的元素构造列表非常简单。需要一个集合而不是列表 - 只需使用Collectors.toList()

下一个示例按年龄对所有人进行分组:

Map<Integer, List<Person>> personsByAge = persons.stream().collect(Collectors.groupingBy(p -> p.age));personsByAge.forEach((age, p) -> System.out.format("age %s: %s\n", age, p));
复制代码

代码产出

age 18: [Max]
age 23: [Peter, Pamela]
age 12: [David]
复制代码

您还可以在流的元素上创建聚合,例如,确定所有人的平均年龄:

Double averageAge = persons.stream().collect(Collectors.averagingInt(p -> p.age));System.out.println(averageAge);
复制代码

代码产出

19.0
复制代码

如果您对更全面的统计信息感兴趣,汇总收集器将返回一个特殊的内置摘要统计信息对象。因此,我们可以简单地确定人的最小,最大和算术平均年龄以及总和和计数。

IntSummaryStatistics ageSummary =persons.stream().collect(Collectors.summarizingInt(p -> p.age));System.out.println(ageSummary);
复制代码

代码产出

IntSummaryStatistics{count=4, sum=76, min=12, average=19.000000, max=23}
复制代码

下一个示例将所有人连接成一个字符串:

String phrase = persons.stream().filter(p -> p.age >= 18).map(p -> p.name).collect(Collectors.joining(" and ", "In Germany ", " are of legal age."));System.out.println(phrase);
复制代码

代码产出

In Germany Max and Peter and Pamela are of legal age.
复制代码

Collect接受分隔符以及可选的前缀和后缀。

为了将流元素转换为映射,我们必须指定如何映射键和值。请记住,映射的键必须是唯一的,否则抛出一个IllegalStateException。您可以选择将合并函数作为附加参数传递以绕过异常:

Map<Integer, String> map = persons.stream().collect(Collectors.toMap(p -> p.age,p -> p.name,(name1, name2) -> name1 + ";" + name2));System.out.println(map);
复制代码

代码产出

{18=Max, 23=Peter;Pamela, 12=David}
复制代码

现在我们知道了一些强大的Collect,让我们尝试构建我们自己的特殊Collect。我们希望将流的所有人转换为单个字符串,该字符串由|管道字符分隔的大写字母组成。为了实现这一目标,我们创建了一个新的Collector.of()

Collector<Person, StringJoiner, String> personNameCollector =Collector.of(() -> new StringJoiner(" | "),          // supplier(j, p) -> j.add(p.name.toUpperCase()),  // accumulator(j1, j2) -> j1.merge(j2),               // combinerStringJoiner::toString);                // finisherString names = persons.stream().collect(personNameCollector);System.out.println(names);// MAX | PETER | PAMELA | DAVID
复制代码

由于Java中的字符串是不可变的,我们需要一个帮助类StringJoiner,让Collect构造我们的字符串。供应商最初使用适当的分隔符构造这样的StringJoiner。累加器用于将每个人的大写名称添加到StringJoiner。组合器知道如何将两个StringJoiners合并为一个。在最后一步中,整理器从StringJoiner构造所需的String。

FlatMap


我们已经学会了如何利用map操作将流的对象转换为另一种类型的对象。Map有点受限,因为每个对象只能映射到另一个对象。但是如果我们想要将一个对象转换为多个其他对象或者根本不转换它们呢?这是flatMap救援的地方。

FlatMap将流的每个元素转换为其他对象的流。因此,每个对象将被转换为由流支持的零个,一个或多个其他对象。然后将这些流的内容放入返回flatMap操作流中。

在我们看到flatMap实际操作之前,我们需要一个适当的类型层

class Foo {String name;List<Bar> bars = new ArrayList<>();Foo(String name) {this.name = name;}
}class Bar {String name;Bar(String name) {this.name = name;}
}
复制代码

接下来,我们利用有关流的知识来实例化几个对象:

List<Foo> foos = new ArrayList<>();// create foos
IntStream.range(1, 4).forEach(i -> foos.add(new Foo("Foo" + i)));// create bars
foos.forEach(f ->IntStream.range(1, 4).forEach(i -> f.bars.add(new Bar("Bar" + i + " <- " + f.name))));
复制代码

现在我们列出了三个foos,每个foos由三个数据组成。

FlatMap接受一个必须返回对象流的函数。所以为了解决每个foo的bar对象,我们只传递相应的函数:

foos.stream().flatMap(f -> f.bars.stream()).forEach(b -> System.out.println(b.name));
复制代码

代码产出

Bar1 <- Foo1
Bar2 <- Foo1
Bar3 <- Foo1
Bar1 <- Foo2
Bar2 <- Foo2
Bar3 <- Foo2
Bar1 <- Foo3
Bar2 <- Foo3
Bar3 <- Foo3
复制代码

如您所见,我们已成功将三个foo对象的流转换为九个bar对象的流。

最后,上面的代码示例可以简化为流操作的单个管道:

IntStream.range(1, 4).mapToObj(i -> new Foo("Foo" + i)).peek(f -> IntStream.range(1, 4).mapToObj(i -> new Bar("Bar" + i + " <- " f.name)).forEach(f.bars::add)).flatMap(f -> f.bars.stream()).forEach(b -> System.out.println(b.name));
复制代码

FlatMap也可用于Java 8中引入的Optional类。Optionals flatMap操作返回另一种类型的可选对象。因此,它可以用来防止令人讨厌的null检查。

这样一个高度分层的结构:

class Outer {Nested nested;
}class Nested {Inner inner;
}class Inner {String foo;
}
复制代码

为了解析foo外部实例的内部字符串,您必须添加多个空值检查以防止可能的NullPointerExceptions:

Outer outer = new Outer();
if (outer != null && outer.nested != null && outer.nested.inner != null) {System.out.println(outer.nested.inner.foo);
}
复制代码

利用选项flatMap操作可以获得相同的行为:

Optional.of(new Outer()).flatMap(o -> Optional.ofNullable(o.nested)).flatMap(n -> Optional.ofNullable(n.inner)).flatMap(i -> Optional.ofNullable(i.foo)).ifPresent(System.out::println);
复制代码

每个调用flatMap返回一个Optional包装所需对象(如果存在)或null不存在。

Reduce


Reduce操作将流的所有元素组合成单个结果。Java 8支持三种不同的reduce方法。第一个将元素流简化为流的一个元素。让我们看看我们如何使用这种方法来确定最老的人:

persons.stream().reduce((p1, p2) -> p1.age > p2.age ? p1 : p2).ifPresent(System.out::println);    // Pamela
复制代码

reduce方法接受一个BinaryOperator累加器函数。这实际上是一个双函数,两个操作数共享同一类型,在这种情况下是Person。双函数类似于函数,但接受两个参数。示例函数比较两个人的年龄,以返回年龄最大的人。

第二种reduce方法接受标识值和BinaryOperator累加器。此方法可用于构造一个新的Person,其中包含来自流中所有其他人的聚合名称和年龄:

Person result =persons.stream().reduce(new Person("", 0), (p1, p2) -> {p1.age += p2.age;p1.name += p2.name;return p1;});System.out.format("name=%s; age=%s", result.name, result.age);
// name=MaxPeterPamelaDavid; age=76
复制代码

第三种reduce方法接受三个参数:标识值,BiFunction累加器和类型的组合器函数BinaryOperator。由于身份值类型不限于Person类型,我们可以利用reduce来确定所有人的年龄总和:

Integer ageSum = persons.stream().reduce(0, (sum, p) -> sum += p.age, (sum1, sum2) -> sum1 + sum2);System.out.println(ageSum);  // 76
复制代码

正如你所看到的结果是76,但是究竟发生了什么?让我们通过一些调试输出扩展上面的代码:

Integer ageSum = persons.stream().reduce(0,(sum, p) -> {System.out.format("accumulator: sum=%s; person=%s\n", sum, p);return sum += p.age;},(sum1, sum2) -> {System.out.format("combiner: sum1=%s; sum2=%s\n", sum1, sum2);return sum1 + sum2;});
复制代码

代码产出

accumulator: sum=0; person=Max
accumulator: sum=18; person=Peter
accumulator: sum=41; person=Pamela
accumulator: sum=64; person=David
复制代码

正如你所看到的,累加器函数完成了所有的工作。它首先以初始恒等值0和第一个person Max被调用。在接下来的三个步骤中,总和随着最后一个步骤的年龄不断增加,人的总年龄达到76岁。

为什么组合器永远不会被调用?并行执行相同的流将解除秘密​​:

Integer ageSum = persons.parallelStream().reduce(0,(sum, p) -> {System.out.format("accumulator: sum=%s; person=%s\n", sum, p);return sum += p.age;},(sum1, sum2) -> {System.out.format("combiner: sum1=%s; sum2=%s\n", sum1, sum2);return sum1 + sum2;});
复制代码

代码产出

accumulator: sum=0; person=Pamela
accumulator: sum=0; person=David
accumulator: sum=0; person=Max
accumulator: sum=0; person=Peter
combiner: sum1=18; sum2=23
combiner: sum1=23; sum2=12
combiner: sum1=41; sum2=35
复制代码

并行执行此流会导致完全不同的执行行为。现在实际上调用了组合器。由于累加器是并行调用的,因此需要组合器来对各个累加值求和。

Java 8 Strem高级操作相关推荐

  1. mongodb java 多条件查询_MongoDB查询之高级操作详解(多条件查询、正则匹配查询等)...

    MongoDB查询之高级操作 语法介绍 MongoDB查询文档使用find()方法,同时find()方法以非结构化的方式来显示所有查询到的文档. -- 1.基本语法 db.collection.fin ...

  2. 编写XML作为配置文件的高级操作库

    编写XML作为配置文件的高级操作库 yipsilon 原创  (参与分:293,专家分:180)   发表:2003-7-29 下午4:26   更新:2003-7-30 上午8:30   版本:1. ...

  3. java ee web高级,Java EE Web高级开发案例

    核心提示:Java EE Web高级开发案例 内容简介:<Java EE Web高级开发案例>充分体现了高等职业教育的特点,突出了理论和实践的紧密结合,以充分掌握基本技术技能和必要的基本知 ...

  4. 使用Java 8 Stream像操作SQL一样处理数据(上)

    转载自 使用Java 8 Stream像操作SQL一样处理数据(上) 几乎每个Java应用都要创建和处理集合.集合对于很多编程任务来说是一个很基本的需求.举个例子,在银行交易系统中你需要创建一个集合来 ...

  5. 深入理解java虚拟机 - jvm高级特性与最佳实践(第三版)_JVM虚拟机面试指南:年薪30W以上高薪岗位需求的JVM,你必须要懂!...

    JVM的重要性 很多人对于为什么要学JVM这个问题,他们的答案都是:因为面试.无论什么级别的Java从业者,JVM都是进阶时必须迈过的坎.不管是工作还是面试中,JVM都是必考题.如果不懂JVM的话,薪 ...

  6. java excel生成_java操作excel表,包括创建、读取、以及修改【via 度娘】

    作者的网站上对它的特征有如下描述: ● 支持Excel 95-2000的所有版本 ● 生成Excel 2000标准格式 ● 支持字体.数字.日期操作 ● 能够修饰单元格属性 ● 支持图像和图表 应该说 ...

  7. java自动化测试语言高级之Java 9 新特性

    java自动化测试语言高级之Java 9 新特性 文章目录 java自动化测试语言高级之Java 9 新特性 Java 9 新特性 Java 9 新特性 Java 9 发布于 2017 年 9 月 2 ...

  8. java自动化测试语言高级之泛型

    java自动化测试语言高级之泛型 文章目录 java自动化测试语言高级之泛型 Java 泛型 Java 泛型 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类 ...

  9. Java Nio 之高级搬砖工(FileChannel)二

    Java Nio 系列 Java Nio 之Buffer Java Nio 之直接内存 Java Nio 之高级搬砖工(FileChannel) 一 Java Nio 之高级搬砖工(FileChann ...

最新文章

  1. Python之父发文,将重构现有核心解析器
  2. 清理恶意插件提高上网速度
  3. DCMTK:类DcmUniqueIdentifier的测试程序
  4. 前凸后翘的步进电机调速算法~
  5. java复习系列[1] - Java 基础
  6. python和java语言的区别
  7. android4.0 禁止横竖屏切换使用 android:configChanges=orientation|keyboardHidden无效
  8. txt简谱制作器上线(个人java程序)
  9. 【CarMaker学习笔记】CarMaker Python API 接口使用方法
  10. 固态硬盘用软件测试掉速严重,SSD固态硬盘掉速怎么办?手动执行TRIM指令缓解固态硬盘掉速方法...
  11. 学习日记| javaScript在网页绘制国际象棋盘
  12. wpsppt怎样让图片模糊_PPT怎么设置图片由模糊到清楚?
  13. 2D图像像素点操作——平移,旋转,缩放 tcy
  14. 测试岗位面试前复习之【测试基础知识篇】
  15. 【翻译】“PE文件格式”1.9版 完整译文(附注释)
  16. 学习布局(21)HTML5新标签
  17. PHP电子商城需求分析,网站(电子商城)设计与实现[原创]
  18. Tomcat 8 性能优化
  19. awk 字符串转时间戳
  20. 计算机电缆的表示方式,各种电缆型号的表示方法

热门文章

  1. 人大金仓数据库查询表数据量和记录数
  2. jzoj5840. Miner(欧拉路径)
  3. [2012黑莓赛] *.bar应用签名步骤 [大二THS_201]
  4. Font 楷体_GB2312 is not available to the JVM. See the Javadoc for more det
  5. Android之Emoji表情开源库
  6. OmniGraffle使用技巧汇总(20150717)
  7. 【视频教程】基于Fragstats的土地利用景观格局分析应用
  8. centos如何创建本地YUM源
  9. 关于Activity
  10. 7 Billion Humans游玩