- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
不像 C# 的 IEnumerable
,其中一个执行管道可以根据需要执行多次,而在 Java 中,一个流只能“迭代”一次。
对终端操作的任何调用都会关闭流,使其无法使用。
这个“功能”带走了很多力量。
我想这不是技术原因。这个奇怪的限制背后的设计考虑是什么?
编辑:为了演示我在说什么,请考虑以下 C# 中快速排序的实现:
IEnumerable<int> QuickSort(IEnumerable<int> ints)
{
if (!ints.Any()) {
return Enumerable.Empty<int>();
}
int pivot = ints.First();
IEnumerable<int> lt = ints.Where(i => i < pivot);
IEnumerable<int> gt = ints.Where(i => i > pivot);
return QuickSort(lt).Concat(new int[] { pivot }).Concat(QuickSort(gt));
}
最佳答案
我对 Streams API 的早期设计有一些记忆,这些记忆可能会阐明设计原理。
早在 2012 年,我们就在语言中添加了 lambda,我们想要一个面向集合或“批量数据”的操作集,使用 lambda 进行编程,以促进并行性。在这一点上,懒惰地将操作链接在一起的想法已经很好地建立了。我们也不希望中间操作存储结果。
我们需要决定的主要问题是链中的对象在 API 中的样子以及它们如何连接到数据源。来源通常是集合,但我们也希望支持来自文件或网络的数据,或者即时生成的数据,例如来自随机数生成器。
现有工作对设计有很多影响。其中比较有影响的是谷歌的 Guava 库和 Scala 集合库。 (如果有人对 Guava 的影响感到惊讶,请注意 Guava 首席开发人员 Kevin Bourrillion 是 JSR-335 Lambda 专家组的成员。)在 Scala 集合上,我们发现 Martin Odersky 的这个演讲特别有趣:Future-Proofing Scala Collections: from Mutable to Persistent to Parallel。 (斯坦福 EE380,2011 年 6 月 1 日。)
我们当时的原型(prototype)设计基于 Iterable
。熟悉的操作 filter
、 map
等是 Iterable
上的扩展(默认)方法。调用一个向链中添加了一个操作并返回另一个 Iterable
。像 count
这样的终端操作会调用 iterator()
沿着链向上到达源,并且这些操作在每个阶段的迭代器中实现。
由于这些是可迭代对象,您可以多次调用 iterator()
方法。那应该怎么办?
如果源是一个集合,这通常可以正常工作。集合是可迭代的,每次调用 iterator()
都会产生一个独立于任何其他 Activity 实例的独特 Iterator 实例,并且每个实例都独立地遍历集合。伟大的。
现在如果源是一次性的,比如从文件中读取行怎么办?也许第一个迭代器应该得到所有的值,但第二个和后续的应该是空的。也许这些值应该在迭代器之间交错。或者也许每个迭代器都应该获得所有相同的值。那么,如果你有两个迭代器并且一个比另一个更早呢?有人将不得不缓冲第二个迭代器中的值,直到它们被读取。更糟糕的是,如果您获得一个 Iterator 并读取所有值,然后才获得第二个 Iterator,该怎么办?值(value)从何而来?是否需要将它们全部缓冲以防万一有人想要第二个迭代器?
显然,在一次性源上允许多个迭代器会引发很多问题。我们没有给他们很好的答案。如果您两次调用 iterator()
会发生什么,我们想要一致的、可预测的行为。这促使我们禁止多次遍历,使管道一次性。
我们还观察到其他人遇到了这些问题。在 JDK 中,大多数 Iterable 都是集合或类集合对象,允许多次遍历。它没有在任何地方指定,但似乎有一个不成文的期望,即 Iterables 允许多次遍历。一个值得注意的异常(exception)是 NIO DirectoryStream 接口(interface)。它的规范包括这个有趣的警告:
While DirectoryStream extends Iterable, it is not a general-purpose Iterable as it supports only a single Iterator; invoking the iterator method to obtain a second or subsequent iterator throws IllegalStateException.
// Scala
val lines = fromString(data).getLines
val registrants = lines.map(Registrant)
registrants.foreach(println)
registrants.foreach(println)
Registrant
个对象并将它们打印两次。除了它实际上只打印一次。结果他认为
registrants
是一个集合,而实际上它是一个迭代器。对
foreach
的第二次调用遇到一个空迭代器,其中的所有值都已耗尽,因此它什么也不打印。
Iterable<?> it = source.filter(...).map(...).filter(...).map(...);
it.into(dest1);
it.into(dest2);
into
操作现在拼写为
collect(toList())
。)
into()
调用将创建一个返回源的迭代器链,执行管道操作,并将结果发送到目标。第二次调用
into()
将创建另一个迭代器链,并再次执行管道操作
。这显然没有错,但它确实具有为每个元素第二次执行所有过滤器和映射操作的效果。我想很多程序员都会对这种行为感到惊讶。
List.filter()
的
List
操作:
The biggest concern here is that too many operations become expensive, linear-time propositions. If you want to filter a list and get a list back, and not just a Collection or an Iterable, you can use
ImmutableList.copyOf(Iterables.filter(list, predicate))
, which "states up front" what it's doing and how expensive it is.
get(0)
或
size()
的成本是多少?对于像
ArrayList
这样的常用类,它们是 O(1)。但是如果你在一个延迟过滤的列表上调用其中一个,它必须在支持列表上运行过滤器,突然这些操作是 O(n)。更糟糕的是,它必须在每次 操作时遍历
上的支持列表。IEnumerable
,我远不是 C# 和 .NET 方面的专家,所以如果我得出任何不正确的结论,我希望得到纠正(温和地)。然而,似乎 IEnumerable
允许多次遍历对不同的源有不同的行为;它允许嵌套 IEnumerable
操作的分支结构,这可能会导致一些重要的重新计算。虽然我理解不同的系统会做出不同的权衡,但这是我们在 Java 8 Streams API 设计中试图避免的两个特征。QuickSort
需要一个 IEnumerable
并返回一个 IEnumerable
,因此在遍历最后一个 IEnumerable
之前实际上不会进行排序。然而,这个调用似乎在构建一个 IEnumerables
的树结构,它反射(reflect)了快速排序会做的分区,但实际上并没有这样做。 (毕竟这是惰性计算。)如果源有 N 个元素,则树的最宽将是 N 个元素宽,并且深度为 lg(N) 级。ints.First()
选择枢轴,比它们看起来更昂贵。在第一层,当然是 O(1)。但是考虑在树深处的右侧边缘的分区。要计算此分区的第一个元素,必须遍历整个源,这是一个 O(N) 操作。但是由于上面的分区是惰性的,它们必须重新计算,需要 O(lg N) 次比较。因此,选择主元将是一个 O(N lg N) 操作,这与整个排序一样昂贵。IEnumerable
之前,我们实际上不会进行排序。在标准的快速排序算法中,每一级分区都会使分区数加倍。每个分区只有一半的大小,因此每个级别保持 O(N) 复杂度。分区树的高度为 O(lg N),因此总工作量是 O(N lg N)。IEnumerable
来构建复杂的计算结构确实很酷。但是,如果它确实像我认为的那样增加了计算复杂性,那么除非非常小心,否则似乎应该避免以这种方式编程。
关于java - 为什么 Java Streams 是一次性的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28459498/
我正在编写一个具有以下签名的 Java 方法。 void Logger(Method method, Object[] args); 如果一个方法(例如 ABC() )调用此方法 Logger,它应该
我是 Java 新手。 我的问题是我的 Java 程序找不到我试图用作的图像文件一个 JButton。 (目前这段代码什么也没做,因为我只是得到了想要的外观第一的)。这是我的主课 代码: packag
好的,今天我在接受采访,我已经编写 Java 代码多年了。采访中说“Java 垃圾收集是一个棘手的问题,我有几个 friend 一直在努力弄清楚。你在这方面做得怎么样?”。她是想骗我吗?还是我的一生都
我的 friend 给了我一个谜语让我解开。它是这样的: There are 100 people. Each one of them, in his turn, does the following
如果我将使用 Java 5 代码的应用程序编译成字节码,生成的 .class 文件是否能够在 Java 1.4 下运行? 如果后者可以工作并且我正在尝试在我的 Java 1.4 应用程序中使用 Jav
有关于why Java doesn't support unsigned types的问题以及一些关于处理无符号类型的问题。我做了一些搜索,似乎 Scala 也不支持无符号数据类型。限制是Java和S
我只是想知道在一个 java 版本中生成的字节码是否可以在其他 java 版本上运行 最佳答案 通常,字节码无需修改即可在 较新 版本的 Java 上运行。它不会在旧版本上运行,除非您使用特殊参数 (
我有一个关于在命令提示符下执行 java 程序的基本问题。 在某些机器上我们需要指定 -cp 。 (类路径)同时执行java程序 (test为java文件名与.class文件存在于同一目录下) jav
我已经阅读 StackOverflow 有一段时间了,现在我才鼓起勇气提出问题。我今年 20 岁,目前在我的家乡(罗马尼亚克卢日-纳波卡)就读 IT 大学。足以介绍:D。 基本上,我有一家提供簿记应用
我有 public JSONObject parseXML(String xml) { JSONObject jsonObject = XML.toJSONObject(xml); r
我已经在 Java 中实现了带有动态类型的简单解释语言。不幸的是我遇到了以下问题。测试时如下代码: def main() { def ks = Map[[1, 2]].keySet()
一直提示输入 1 到 10 的数字 - 结果应将 st、rd、th 和 nd 添加到数字中。编写一个程序,提示用户输入 1 到 10 之间的任意整数,然后以序数形式显示该整数并附加后缀。 public
我有这个 DownloadFile.java 并按预期下载该文件: import java.io.*; import java.net.URL; public class DownloadFile {
我想在 GUI 上添加延迟。我放置了 2 个 for 循环,然后重新绘制了一个标签,但这 2 个 for 循环一个接一个地执行,并且标签被重新绘制到最后一个。 我能做什么? for(int i=0;
我正在对对象 Student 的列表项进行一些测试,但是我更喜欢在 java 类对象中创建硬编码列表,然后从那里提取数据,而不是连接到数据库并在结果集中选择记录。然而,自从我这样做以来已经很长时间了,
我知道对象创建分为三个部分: 声明 实例化 初始化 classA{} classB extends classA{} classA obj = new classB(1,1); 实例化 它必须使用
我有兴趣使用 GPRS 构建车辆跟踪系统。但是,我有一些问题要问以前做过此操作的人: GPRS 是最好的技术吗?人们意识到任何问题吗? 我计划使用 Java/Java EE - 有更好的技术吗? 如果
我可以通过递归方法反转数组,例如:数组={1,2,3,4,5} 数组结果={5,4,3,2,1}但我的结果是相同的数组,我不知道为什么,请帮助我。 public class Recursion { p
有这样的标准方式吗? 包括 Java源代码-测试代码- Ant 或 Maven联合单元持续集成(可能是巡航控制)ClearCase 版本控制工具部署到应用服务器 最后我希望有一个自动构建和集成环境。
我什至不知道这是否可能,我非常怀疑它是否可能,但如果可以,您能告诉我怎么做吗?我只是想知道如何从打印机打印一些文本。 有什么想法吗? 最佳答案 这里有更简单的事情。 import javax.swin
我是一名优秀的程序员,十分优秀!