向您展示一些可以非常方便地使用Java Stream流的场景以及如何使用它们的示例。

本文基于标准Java库java.util.stream。它既与反应流无关,也与诸如Vavr之类的其他流实现无关。另外,我将不介绍诸如并行执行之类的流的高级细节。

首先,让我们简要讨论与集合相比独特的流功能。尽管存在一些相似之处,但差异是很大的,您不应将流仅视为库中的另一种集合。

根据java.util.stream 的文档,最重要的功能是:没有存储空间,可能是无限制的 -集合是现成的数据结构,而流表示产生数据的能力,通常在创建流时甚至不存在。由于不存储流中的数据,因此我们可以创建几乎不确定的流,或者可以更实际地对其重新措辞,我们可以让消费者决定要从流中读取多少个元素,从生产者的角度来看,它可能是不确定的(例如new Random().ints())。

懒惰加载 —在定义流时暂停许多操作(例如过滤,映射),并且仅在使用者决定使用流中的数据时才执行

本质上是实用的 -由于您已经具有使用流的经验,因此您可能会注意到处理流中的数据是为每个步骤(例如过滤器或映射)创建新流,而不是修改源数据

消耗性 -您只能读取一次流,然后与可以多次读取的集合不同,它变为“消耗性”

现在让我们看看我们可以用流解决什么问题。

处理大量数据

假设,我们必须将数据从外部服务复制到我们的数据库中。要复制的数据量可以任意大。我们无法获取所有数据,无法将其存储在一个集合中,然后保存在数据库中,因为这可能会耗尽堆内存。我们必须分批处理数据,并设计外部服务客户端和数据库存储之间的接口。由于流不存储日期,因此可以使用它安全地处理所需的数据量。

在示例(及以下所有示例)中,我们将使用java.util.stream.Stream接口的静态方法来构建流。用Java构建流的最强大,最灵活的方法是实现Spliterator接口,然后使用StreamSupport类将其包装为流。但是,正如我们所看到的,Stream在许多情况下,接口中的静态工厂方法就足够了。

假定一个简单的API从支持分页的外部服务(例如,REST服务,数据库)中获取数据。该API最多可limit从提取项目offset。迭代地使用API​​,我们可以根据需要获取尽可能多的数据

interface ExternalService {

List fetch(int offset, int limit);

}

现在,我们可以使用API​​提供数据流,并将API的使用者与分页API隔离开:

class Service {

private final ExternalService externalService;

public Stream stream(int size, int batchSize) {

var cursor = new Cursor();

return Stream

.generate(() -> next(cursor, size, batchSize))

.takeWhile(not(List::isEmpty))

.flatMap(List::stream);

}private List next(Cursor cursor, int size, int batchSize){

var fetchSize = Math.min(size - cursor.offset, batchSize);

var result = externalService.fetch(cursor.offset, fetchSize);

cursor.inc(result.size());

return result;

}

}

Cursor 握有当前偏移量offset:

private static class Cursor {

private int offset;

void inc(int by) {

offset += by;

}

我们使用Stream.generate()方法构建无限流,其中每个元素由流提供者创建。流元素是从REST API获取的页面List。将为每个流创建Cursor类的实例,以跟踪获取的元素的进度。

Stream.takeWhile()方法用于检测的最后一页,最后返回的数据流T,而不是List。

我们使用flatMap扁平化流。尽管在某些情况下,保留批处理(例如将整个页面保存在一个事务中)可能很有用。

现在,我们可以使用Service.stream(size, batchSize)来检索任意长流,而无需任何分页API的知识(我们决定公开batchSize参数,但这是一个设计决策)。在任何时间点,内存消耗都受到批处理大小的限制。使用者可以一一处理数据,将其保存在数据库中,或者再次进行批处理(批处理大小可能不同)。

快速访问(不完整)数据

假设我们有一个耗时的操作,必须对数据的每个元素执行该操作,并且计算要花费时间t。对于n元素,使用者必须等待t * n才能接收到计算结果。例如,如果用户正在等待带有计算结果的表,则可能是一个问题。我们希望在显示第一结果时立即显示它们,而不是等待所有结果的计算并立即提交表。

public class Producer1 {

private Stream buildStream() {

return Stream.of("a", "b", "c"); }private String expensiveStringDoubler(String input){

return input + input;

}public Stream stream(){

return buildStream().map(this::expensiveComputation);

}

}

消费者:

stream().forEach(System.out::println)

输出:

Processing of: a aa Processing of: b …

输出:

Processing of: a aa Processing of: b …

如我们所见,在开始处理下一个元素之前,用户可以使用第一个元素“ aa ”的处理结果,但是计算仍然是流的生产者责任。换句话说,消费者决定何时以及是否应该执行计算,但是生产者仍然负责如何执行计算。

您可能会认为这很容易,并且不需要流。当然,您是对的,让我们看一下:

public class Producer1Classic {

public List data() {

return List.of("a", "b", "c", "d", "e", "f"); }public String expensiveStringDoubler(String input){

return input + input;

}

}

消费者:

var producer = new Producer1Classic();

for (String element : producer.data()) {

System.out.println(producer.expensiveComputation(element));

}

同样的效果,但是实际上我们已经重新发明了轮子,我们的实现模仿了stream的祖先- Iterator并且我们失去了stream的API的优势。

避免过早计算

再次假设我们要对每个流元素执行耗时的操作。在某些情况下,API的使用者无法提前说出需要多少数据。例如:用户取消了数据加载

在数据处理过程中发生错误,无需处理其余数据

消费者读取数据直到满足条件,例如第一个正值

由于流的惰性,在这种情况下可以避免一些计算。

private Stream buildStream() {

return new Random().doubles().boxed();

}private Double expensiveComputation(Double input){

return input / 2;

}public Stream stream(){

return buildStream().map(this::expensiveComputation);

}

消费者:

stream().peek(System.out::println).filter(value -> value > 0.4).findFirst();

在该示例中,使用者读取数据,直到该值大于0.4。生产者并不了解消费者的这种逻辑,但它只计算必要的项目。逻辑(例如条件)可以在用户端独立更改。

API易于使用

使用流而不是自定义API设计还有另一个原因。流是标准库的一部分,并为许多开发人员所熟知。在我们的API中使用流使其他开发人员更容易使用该API。

其他注意事项

错误处理

传统的错误处理不适用于Streams。由于实际处理将推迟到需要时进行,因此构造流时不会引发异常。基本上,我们有两个选择:引发RuntimeException-终止方法(例如forEach)将引发异常

将元素包装到一个对象中,该对象表示正在处理的元素的当前状态,例如TryVavr库中的特殊类(博客中的详细信息)

资源管理

有时我们必须使用一种资源来提供流数据(例如,外部服务中的会话),并且我们想在流处理完成时将其释放。幸运的是,流实现了Autoclosable接口,我们可以在try-with-resources语句中使用流,从​​而使资源管理变得非常容易。我们要做的就是使用onClose方法在流中注册一个钩子。当流关闭时,该挂钩将自动被调用。

private Stream buildStream() {

return new Random().doubles().boxed();

}private Double expensiveComputation(Double input){

if (input > 0.8) throw new RuntimeException("Data processing exception"); return input / 2;

}public Stream stream(){

return buildStream().map(this::expensiveComputation).onClose(()-> System.out.println("Releasing resources…

消费者:

try (Stream stream = stream()){

stream.forEach(System.out::println);

}

输出:

0.2264004802916616

0.32777949557515484

Releasing resources…

Exception in thread “main” java.lang.RuntimeException: Data processing exception

在该示例中,当发生数据处理异常时,流将通过try-with-resources语句自动关闭,并调用已注册的处理程序。在示例输出中,我们可以看到Releasing resources…处理程序打印的消息。

总结流不是集合。

流可以帮助我们解决以下问题:*处理大量数据*快速访问(不完整的)数据*避免过早计算

构建流并不难。

我们必须注意错误处理。

支持资源管理。

java collection api_Java Stream和Collection比较:何时以及如何从Java API返回?相关推荐

  1. java中的Stream

    1. Stream流 1.1. Stream流引入 Stream流完全不是I/O流,按照流水线处理方式来考虑代码中的思想. JDK1.8 之后,我们拥有了Lambda表达式,让代码的中心偏向解决实际问 ...

  2. java.lang.IllegalStateException: stream has already been operated upon or closed

    1. 错误描述 static int [] intArr = new int[]{30,96,23,69,85,62,12,99,11}; IntStream intStream =Arrays.st ...

  3. java多线程运行结果写入文件错误java.io.IOException: Stream closed

    今天在写多线程程序的时候,需要将每个线程运行的结果写入同一个文件,用的是FileWriter,最后在关闭写入流的时候出现如下问题: java.io.IOException: Stream closed ...

  4. java steam collect_Java8 如何进行stream reduce,collection操作

    在java8 JDK包含许多聚合操作(如平均值,总和,最小,***,和计数),返回一个计算流stream的聚合结果.这些聚合操作被称为聚合操作. JDK除返回单个值的聚合操作外,还有很多聚合操作返回一 ...

  5. java之集合框架一Collection接口

    1.集合的由来: 我们学习的是面向对象的语言.而面向对象语言对事物的描述是通过对象体现的.为了方便对多个对象进行操作,我们就必须把多个对象进行存储.而要想存储多个对象,就不能是一个基本的变量,而应该是 ...

  6. java集合类根接口:Collection和Map

    本文转载自:https://www.cnblogs.com/yeya/p/9955499.html 前言 在前文中我们了解了几种常见的数据结构,这些数据结构有着各自的应用场景,并且被广泛的应用于编程语 ...

  7. 1.14 Stream操作Collection集合

    使用Java 8新增的Stream操作Collection集合 Java 8 还新增了 Stream.IntStream.LongStream.DoubleStream 等流式 API,这些 API ...

  8. Stream操作Collection集合

    独立使用 Stream 的步骤如下: 1 . 使用 Stream 或 XxxStream 的 builder() 类方法创建该 Stream 对应的 Builder. 2 . 重复调用 Builder ...

  9. Java 集合学习笔记:Collection

    Java 集合学习笔记:Collection UML 简介 方法和说明 JDK8 新增 `default` 方法 AbstractCollection isEmpty contains(Object ...

最新文章

  1. 去除文本框点击的背影
  2. sys.stderr.write(f“ERROR: {exc}“) SyntaxError: invalid syntax错误
  3. C#者重建C++之路 - 运行机制的差异
  4. java.net.SocketException: Unrecognized Windows Sockets error: 0: JVM_Bind
  5. sketch浮动布局_使用智能布局和调整大小在Sketch中创建更好的可重用符号
  6. 空间皮肤代码_不废话,看我20行代码搞定色块提取与定位…….
  7. 全网首发:gstreamer如何接入RTSP流(IP摄像头)的代码范例
  8. Facade(装饰模式)
  9. 基于OpenCV的银行卡号识别系统实现(三)-----输入图像的处理+代码实现
  10. TerraSolid工具试用系列2----TerraScan点云滤波(从点云中提取地面点)备注
  11. 向量的各种积,都有哪些,分别如何表示
  12. 程序员根本不是稳定工作!
  13. 参加科学教师与计算机培训总结,小学科学教师培训总结
  14. 大数据如何改变我们的国庆假期?
  15. 学而优则仕:中国古代政治原生态(转自 百度 读书吧)
  16. LeetCode 2315. 统计星号
  17. c#自定义事件以及与其他事件绑定
  18. python南开大学王恺mooc_Python爬虫示例(正则表达式、靓汤、selenium),程序,BeautifulSoupselenium...
  19. 二分类最优阈值确定_一文搞懂分类算法中常用的评估指标
  20. 美国加州伯克利大学计算机专业排名,美国加州大学伯克利分校计算机专业排名...

热门文章

  1. Swagger+Spring mvc生成Restful接口文档
  2. centos7 搭建Docker Registry
  3. 微软私有云分享(R2)22 计算机配置文件与基础设置
  4. 为UIKeyboardTypeNumberPad增加自定义按键
  5. 安装DNN时,数据库连接字符串的设置
  6. 264标准中高复杂度的三方面
  7. 腐蚀函数cvErode 和 膨胀函数cvDilate
  8. 【pyradiomics学习】——安装pyradiomics以及简单示例
  9. 学习笔记(46):Python实战编程-protocol
  10. SLAM后端优化之-核函数