java8 streams_Java 8 Friday:使用Streams API时的10个细微错误

java8 streams

在Data Geekery ,我们喜欢Java。 而且,由于我们真的很喜欢jOOQ的流畅的API和查询DSL ,我们对Java 8将为我们的生态系统带来什么感到非常兴奋。

Java 8星期五

每个星期五,我们都会向您展示一些不错的教程风格的Java 8新功能,这些功能利用了lambda表达式,扩展方法和其他出色的功能。 您可以在GitHub上找到源代码 。

使用Streams API时的10个细微错误

我们已经完成了所有SQL错误列表:

  • Java开发人员在编写SQL时常犯的10个错误
  • Java开发人员在编写SQL时犯的10个常见错误
  • Java开发人员在编写SQL时再犯的10个常见错误(您不会相信最后一个)

但是我们还没有用Java 8列出前10个错误列表! 在今天的场合( 13日星期五 ),我们将赶上使用Java 8时应用程序中出现的问题(这不会发生在我们身上,因为我们将Java 6留在了另一个Java 6上)而)。

1.意外重用流

想打赌,这至少每个人都会发生一次。 像现有的“流”(例如InputStream )一样,您只能使用一次流。 以下代码不起作用:

IntStream stream = IntStream.of(1, 2);
stream.forEach(System.out::println);// That was fun! Let's do it again!
stream.forEach(System.out::println);

您将获得:

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

因此在使用流时要小心。 只能执行一次。

2.意外创建“无限”流

您无需注意即可轻松创建无限流。 请看以下示例:

// Will run indefinitely
IntStream.iterate(0, i -> i + 1).forEach(System.out::println);

如果您将流设计为无限的,那么流的全部要点就是事实。 唯一的问题是,您可能不需要这样做。 因此,请确保始终设置适当的限制:

// That's better
IntStream.iterate(0, i -> i + 1).limit(10).forEach(System.out::println);

3.意外地创建“微妙”的无限流

我们不能这么说。 您最终意外地创建无限流。 以以下流为例:

IntStream.iterate(0, i -> ( i + 1 ) % 2).distinct().limit(10).forEach(System.out::println);

所以…

  • 我们生成交替的0和1
  • 那么我们只保留不同的值,即单个0和单个1
  • 那么我们将流的大小限制为10
  • 然后我们消耗它

好吧…… distinct()操作不知道提供给iterate()方法的函数只会产生两个不同的值。 它可能会期望更多。 因此它将永远消耗流中的新值,并且永远不会达到limit(10) 。 不幸的是,您的应用程序停顿了。

4.意外地创建“微妙”的并行无限流

我们确实需要坚持,您可能会意外地尝试消耗无限的流。 让我们假设您认为 distinct()操作应并行执行。 您可能正在编写:

IntStream.iterate(0, i -> ( i + 1 ) % 2).parallel().distinct().limit(10).forEach(System.out::println);

现在,我们已经看到,这种情况将永远发生。 但至少在以前,您仅消耗计算机上的一个CPU。 现在,您可能会消耗其中的四个,可能会意外地无限消耗流,从而几乎占据整个系统。 真不好 之后,您可能可以硬重启服务器/开发计算机。 在爆炸之前,最后查看一下我的笔记本电脑的外观:

如果我是笔记本电脑,这就是我想要的方式。

如果我是笔记本电脑,这就是我想去的方式。

5.混合操作顺序

那么,为什么我们坚持要您绝对意外地创建无限流? 这很简单。 因为您可能只是偶然地这样做。 如果您切换limit()distinct()的顺序,则可以完美地使用上述流:

IntStream.iterate(0, i -> ( i + 1 ) % 2).limit(10).distinct().forEach(System.out::println);

现在产生:

0
1

为什么? 因为我们首先将无限流限制为10个值(0 1 0 1 0 1 0 1 0 1),然后再将有限流减小为无限个流中包含的不同值(0 1)。

当然,这在语义上可能不再正确,因为您确实希望从一组数据中获得前10个不同的值(您刚好“忘记”了数据是无限的)。 没有人真正想要10个随机值,然后才将它们减小到与众不同。

如果您来自SQL背景,则可能不会期望有这种差异。 以SQL Server 2012为例。 以下两个SQL语句相同:

-- Using TOP
SELECT DISTINCT TOP 10 *
FROM i
ORDER BY ..-- Using FETCH
SELECT *
FROM i
ORDER BY ..
OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY

因此,作为SQL专家,您可能没有意识到流操作顺序的重要性。

jooq在Java中编写SQL的最佳方法

6.再次混合操作顺序

说到SQL,如果您是MySQL或PostgreSQL人,则可能会习惯LIMIT .. OFFSET子句。 SQL充满了微妙的怪癖,这就是其中之一。 该OFFSET子句应用首先 ,在SQL Server 2012中的(即建议的SQL:2008标准的)语法。

如果将MySQL / PostgreSQL方言直接转换为流,则可能会出错:

IntStream.iterate(0, i -> i + 1).limit(10) // LIMIT.skip(5)   // OFFSET.forEach(System.out::println);

以上收益

5
6
7
8
9

是。 它不会在9之后继续,因为现在应用limit() ,生成(0 1 2 3 4 5 6 7 8 9)。 之后应用skip() ,将流减少到(5 6 7 8 9)。 不是您可能想要的。

注意LIMIT .. OFFSET"OFFSET .. LIMIT"陷阱!

7.使用过滤器遍历文件系统

以前我们已经在博客上写过 。 似乎一个好主意是使用过滤器遍历文件系统:

Files.walk(Paths.get(".")).filter(p -> !p.toFile().getName().startsWith(".")).forEach(System.out::println);

上面的流似乎仅在非隐藏目录(即不以点开头的目录)中移动。 不幸的是,您再次犯了#5和#6错误。 walk()已经生成了当前目录的整个子目录流。 虽然懒惰,但逻辑上包含所有子路径。 现在,过滤器将正确过滤出名称以点“。”开头的路径。 例如.git.idea将不属于结果流。 但是这些路径将是: .\.git\refs.\.idea\libraries 。 不是你想要的。

现在,不要通过编写以下内容解决此问题:

Files.walk(Paths.get(".")).filter(p -> !p.toString().contains(File.separator + ".")).forEach(System.out::println);

尽管这将产生正确的输出,但仍将通过遍历完整的目录子树,然后递归到“隐藏”目录的所有子目录来实现。

我猜您将不得不再次使用旧的JDK 1.0 File.list() 。 好消息是, FilenameFilterFileFilter都是功能接口。

8.修改流的后备集合

在迭代List ,一定不要在迭代主体中修改相同的列表。 在Java 8之前确实如此,但是对于Java 8流,它可能变得更加棘手。 考虑以下来自0..9的列表:

// Of course, we create this list using streams:
List<Integer> list = 
IntStream.range(0, 10).boxed().collect(toCollection(ArrayList::new));

现在,假设我们要在使用每个元素时将其删除:

list.stream()// remove(Object), not remove(int)!.peek(list::remove).forEach(System.out::println);

有趣的是,这将适用于某些元素! 您可能获得的输出是以下内容:

0
2
4
6
8
null
null
null
null
null
java.util.ConcurrentModificationException

如果我们在捕获到该异常之后对列表进行了自省,那么将会发现一个有趣的发现。 我们会得到:

[1, 3, 5, 7, 9]

嘿,它对所有奇数都有效。 这是一个错误吗? 不,它看起来像个功能。 如果您正在研究JDK代码,则可以在ArrayList.ArraListSpliterator找到以下注释:

/** If ArrayLists were immutable, or structurally immutable (no* adds, removes, etc), we could implement their spliterators* with Arrays.spliterator. Instead we detect as much* interference during traversal as practical without* sacrificing much performance. We rely primarily on* modCounts. These are not guaranteed to detect concurrency* violations, and are sometimes overly conservative about* within-thread interference, but detect enough problems to* be worthwhile in practice. To carry this out, we (1) lazily* initialize fence and expectedModCount until the latest* point that we need to commit to the state we are checking* against; thus improving precision.  (This doesn't apply to* SubLists, that create spliterators with current non-lazy* values).  (2) We perform only a single* ConcurrentModificationException check at the end of forEach* (the most performance-sensitive method). When using forEach* (as opposed to iterators), we can normally only detect* interference after actions, not before. Further* CME-triggering checks apply to all other possible* violations of assumptions for example null or too-small* elementData array given its size(), that could only have* occurred due to interference.  This allows the inner loop* of forEach to run without any further checks, and* simplifies lambda-resolution. While this does entail a* number of checks, note that in the common case of* list.stream().forEach(a), no checks or other computation* occur anywhere other than inside forEach itself.  The other* less-often-used methods cannot take advantage of most of* these streamlinings.*/

现在,检查当我们告诉流产生sorted()结果时会发生什么:

list.stream().sorted().peek(list::remove).forEach(System.out::println);

现在将产生以下“预期”输出

0
1
2
3
4
5
6
7
8
9

和流消费后的清单? 它是空的:

[]

因此,所有元素都将被消耗并正确删除。 sorted()操作是“有状态中间操作” ,这意味着后续操作不再对后备集合进行操作,而是对内部状态进行操作。 现在从列表中删除元素是“安全的”!

好吧,我们真的可以吗? 让我们继续进行parallel()sorted()移除:

list.stream().sorted().parallel().peek(list::remove).forEach(System.out::println);

现在产生:

7
6
2
5
8
4
1
0
9
3

并且列表包含

[8]

真是的 我们没有删除所有元素! 解决此流难题的任何人都可以免费获得啤酒( 和jOOQ贴纸 )!

这一切看起来都是相当随机和微妙的,我们只能建议您在使用流时不要真正修改后备集合。 就是行不通。

9.忘记实际消耗流

您认为以下信息流有什么作用?

IntStream.range(1, 5).peek(System.out::println).peek(i -> { if (i == 5) throw new RuntimeException("bang");});

阅读此书时,您可能会认为它将打印(1 2 3 4 5),然后引发异常。 但这是不正确的。 它什么也不会做。 流只是坐在那里,从未被消耗过。

与任何流畅的API或DSL一样,您实际上可能会忘记调用“终端”操作。 当您使用peek()时尤其如此,因为peek()forEach()非常相似。

当您忘记调用execute()fetch()时, jOOQ可能会发生相同的情况:

DSL.using(configuration).update(TABLE).set(TABLE.COL1, 1).set(TABLE.COL2, "abc").where(TABLE.ID.eq(3));

哎呀。 没有execute()

jooq在Java-small1中编写SQL的最佳方法


是的,“最佳”方法-1-2次警告!

10.并行流死锁

现在这才是真正的礼物!

如果您未正确同步所有事物,则所有并发系统都可能陷入死锁。 虽然找不到现实的例子很明显,但找到强制的例子很明显。 保证下面的parallel()流会陷入死锁:

Object[] locks = { new Object(), new Object() };IntStream.range(1, 5).parallel().peek(Unchecked.intConsumer(i -> {synchronized (locks[i % locks.length]) {Thread.sleep(100);synchronized (locks[(i + 1) % locks.length]) {Thread.sleep(50);}}})).forEach(System.out::println);

请注意Unchecked.intConsumer()的使用,该函数将功能性IntConsumer接口转换为org.jooq.lambda.fi.util.function.CheckedIntConsumer ,允许抛出已检查的异常。

好。 您的机器运气不好。 这些线程将永远被阻塞!

好消息是,用Java编写死锁的教科书示例从未如此简单!

有关更多详细信息,另请参见Brian Goetz对Stack Overflow的此问题的回答 。

结论

借助流和功能性思维,我们将遇到大量新的,细微的错误。 这些错误很少可以预防,除非通过实践和保持专注。 您必须考虑如何订购您的手术。 您必须考虑流是否可能是无限的。

流(和lambda)是一个非常强大的工具。 但是首先需要掌握的工具。

翻译自: https://www.javacodegeeks.com/2014/06/java-8-friday-10-subtle-mistakes-when-using-the-streams-api.html

java8 streams

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/340552.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python带参数装饰器 函数名_python 全栈开发,Day11(函数名应用,闭包,装饰器初识,带参数以及带返回值的装饰器)...

一、函数名应用函数名是什么&#xff1f;函数名是函数的名字&#xff0c;本质&#xff1a;变量&#xff0c;特殊的变量。函数名()&#xff0c;执行此函数。python 规范写法1. #后面加一个空格&#xff0c;再写内容&#xff0c;就没有波浪线了。2.一行代码写完&#xff0c;下面一…

python逐行写入excel_快来看看Python如何玩转Excel

来源&#xff1a;ID(innerV)如何用Python来操作Excel文件呢&#xff1f;首先&#xff0c;使用pip 包管理器来安装两个包&#xff0c;安装命令&#xff1a;pip install xlrd pip install xlwt我们来看读取excel的例子&#xff0c;第1行&#xff0c;import 导入xlrd包第4行&#…

Java面试准备:15个Java面试问题

并非所有的访谈都将重点放在算法和数据结构上—通常&#xff0c;访谈通常只侧重于您声称是专家的语言或技术。在此类访谈中&#xff0c;通常没有任何“陷阱”问题&#xff0c;而是它们要求您利用内存和使用该语言的经验–换句话说&#xff0c;它们测试您对编程语言的了解。 但…

mysql排插问题_MySQL一次数据插入故障记录

某天突然收到报警&#xff0c;数据库大量事务等待&#xff0c;进到数据库后发线大量的插入操作被阻塞&#xff0c;且都是同一个表的。通过 show engine innodb status 发现插入操作都是在等待索引 idx_create_time(create_time) 的 insert intention lock(跟 gap 锁互斥)&#…

纯净pe工具_微PE工具箱2.0

(特殊时期&#xff0c;在家时间多一些&#xff0c;突然想到多年的公众号&#xff0c;重启试试&#xff0c;嗯就先每一天推荐一个软件吧)微PE工具箱(WinPE)是一款非常好用的PE系统(独立的预安装环境)&#xff0c;非常纯净&#xff0c;是装机维护得力的助手。安装简单&#xff0c…

sping jdbc 链接mysql_Spring Boot JDBC 连接数据库示例

文本将对在spring Boot构建的Web应用中&#xff0c;基于MySQL数据库的几种数据库连接方式进行介绍。包括JDBC、JPA、MyBatis、多数据源和事务。JDBC 连接数据库1、属性配置文件(application.properties)spring.datasource.urljdbc:mysql://localhost:3306/testspring.datasourc…

二分查找递归与非递归的时间比较_我们说一说Python的查找算法!

相信大家在面试开发岗和算法岗时&#xff0c;评委最喜欢问的就是&#xff1a;您能给我说一下查找和排序算法有哪些&#xff1f;今天咱们就说一说Python中最常用的查找算法&#xff0c;下期我们再推出排序算法。首先要明白查找是查什么&#xff1f;我们希望能给定一个值&#xf…

jsf 自定义属性_如何在JSF中实现自定义密码强度指示器

jsf 自定义属性使用JavaScript验证密码强度是一项常见任务。 在本文中&#xff0c;我将展示如何向基于JSF的Web应用程序添加密码强度指示器。 的 PrimeFaces中的密码组件已经具有密码强度的反馈指示符&#xff0c;但是它有两个主要缺点&#xff1a; 反馈指示器没有响应&#…

OAuth 2.0 Java指南:5分钟保护您的应用程序安全

使用Okta的身份管理平台轻松部署您的应用程序 使用Okta的API在几分钟之内即可对任何应用程序中的用户进行身份验证&#xff0c;管理和保护。 今天尝试Okta。 现代应用程序依赖于用户身份验证&#xff0c;但是它可能给Java开发人员带来困难的挑战&#xff0c;以及一系列特定于框…

flutter从0到1构建大前端应用 pdf_前端骨架屏都是如何生成的

作者&#xff1a;SHERlocked93转发链接&#xff1a;https://mp.weixin.qq.com/s/j2XzwLPnalDCNaKkfjH-0Q前言相比于早些年前后端代码紧密耦合、后端工程师还得写前端代码的时代&#xff0c;如今已发展到前后端分离&#xff0c;这种开发方式大大提升了前后端项目的可维护性与开发…

成为Java流大师–第1部分:创建流

在许多情况下&#xff0c;声明性代码&#xff08;例如&#xff0c;具有Streams的功能组合&#xff09;可提供出色的代码指标。 通过本动手实验文章系列进行编码&#xff0c;并成为Java Streams的主教练&#xff0c;从而成为一名更好的Java程序员。 Streams的整个想法是代表一个…

java 6 基础教程_Java小白入门教程(6)——循环语句

提纲&#xff1a;1、循环结构2、while循环3、do-while循环4、for循环5、break语句6、continue语句7、循环嵌套8、作业一、循环结构1.1 概念条件满足&#xff0c;某些代码会被反复多次的执行。条件不成立了&#xff0c;循环结束。0-n次。1.2 为什么使用循环开发中可能会把某些代…

事件触发控制_SystemVerilog线程控制与通信

01线程控制1.概述线程&#xff0c;即独立运行的程序&#xff1b;线程需要被触发执行&#xff0c;可以结束或者不结束&#xff1b;在module中的initial和always&#xff0c;都可以看作独立的线程&#xff0c;他们在仿真0时刻开始&#xff0c;而选择结束或者不结束&#xff1b;在…

java必读书籍_最佳5本Java性能调优书籍–精选,必读

java必读书籍为什么Java开发人员应该阅读有关性能调优的书&#xff1f; 当我很久以前第一次面对这个问题时&#xff0c;我以为以后会做&#xff0c;但是我很长一段时间都没有回过头来。 仅当我在用Java编写的任务关键型服务器端财务应用程序中遇到严重的性能和可伸缩性问题时&a…

成为Java流大师–第4部分:数据库流

SQL一直是一种声明性语言&#xff0c;而Java长期以来势在必行。 Java流改变了游戏规则。 通过本动手文章编写您的方式&#xff0c;并学习如何使用Java流对RDBMS数据库执行声明性查询&#xff0c;而无需编写任何SQL代码。 您会发现&#xff0c;Java流和SQL命令的动词之间有着惊人…

unsafehelper java_Java 9中将移除 Sun.misc.Unsafe

灾难将至&#xff0c;Java 9中将移除 Sun.misc.UnsafeOracle 正在计划在Java 9中去掉 sun.misc.Unsafe API。 这绝对将是一场灾难&#xff0c;有可能会彻底破坏整个 java 生态圈。 几乎每个使用 java开发的工具、软件基础设施、高性能开发库都在底层使用了 sun.misc.Unsafe。 下…

java 根据类名示例化类_如何使用示例从Java中的类路径加载资源

java 根据类名示例化类Java中的类路径不仅用于加载.class文件&#xff0c;而且还可以用于加载资源&#xff0c;例如属性文件&#xff0c;图像&#xff0c;图标&#xff0c;缩略图或任何二进制内容。 Java提供了API来将这些资源读取为InputStream或URL。 假设您在项目的config文…

java课程设计进程管理_Java课设总结(个人版)

使用物理引擎JBox2D完成游戏关卡的各个物理状态模拟根据物理引擎设计Bird,Pig,Brick等游戏中出现的可运动刚体类建立JBox2d的工具类以实现###本人对这次课设的看法与吐槽 1.课设内容的脑洞是我在看完17级学长的游戏课设之后想出的 当时还没学java网络编程的内容&#xff0c;误以…

二叉树层次遍历c语言_[LeetCode] 107. 二叉树的层次遍历 II

题目链接 : https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/题目描述:给定一个二叉树&#xff0c;返回其节点值自底向上的层次遍历。 &#xff08;即按从叶子节点所在层到根节点所在的层&#xff0c;逐层从左向右遍历&#xff09;例如&#xff1a; 给…

使用测微计收集应用程序指标

什么是千分尺&#xff1f; 千分尺是一个简单的外观&#xff0c;用于以供应商中立的方式收集Java应用程序中的指标。 您可以考虑使用SLF4J作为指标。 Micrometer内置了对许多不同指标后端的支持&#xff0c;包括Atlas&#xff0c;Datadog&#xff0c;Elastic&#xff0c;JMX等。…