背景
虽然这个问题看起来很简单,但实际的答案需要一些背景知识才能理解。如果你想跳到结论,向下滚动…
选择你的比较点-基本功能
使用基本概念,c#的IEnumerable概念与Java的Iterable更接近,后者可以创建任意数量的迭代器。IEnumerables创建ienumerator。Java的Iterable创建迭代器
这两个概念的历史是相似的,因为IEnumerable和Iterable都有一个基本动机,即允许在数据集合的成员上进行“for-each”风格的循环。这是一种过度简化,因为他们都允许更多的东西,而且他们也是通过不同的进程到达这个阶段的,但无论如何这是一个重要的共同特征。
让我们比较一下这个特性:在两种语言中,如果一个类实现了IEnumerable/Iterable,那么这个类必须至少实现一个方法(对于c#,它是GetEnumerator,对于Java,它是iterator())。在每种情况下,返回的实例(IEnumerator/Iterator)允许您访问数据的当前成员和后续成员。该特性用于for-each语言语法。
选择您的比较点-增强功能
c#中的IEnumerable已经被扩展到允许许多其他语言特性(主要与Linq相关)。添加的功能包括选择、投影、聚合等。这些扩展在集合论中有很强的使用动机,类似于SQL和关系数据库的概念。
Java 8还增加了一些功能,可以使用Streams和Lambdas进行一定程度的函数式编程。注意,Java 8流主要不是由集合论驱动的,而是由函数式编程驱动的。无论如何,两者有很多相似之处。
这是第二点。对c#的增强是作为对IEnumerable概念的增强来实现的。然而,在Java中,所做的增强是通过创建Lambdas和Streams的新基本概念来实现的,然后还创建了一种相对简单的方法来从迭代器和可迭代对象转换为Streams,反之亦然。
因此,比较IEnumerable和Java的Stream概念是不完整的。您需要将其与Java中的组合流和集合API进行比较。
在Java中,流与可迭代对象或迭代器不同
流不像迭代器那样被设计来解决问题:
迭代器是一种描述数据序列的方法。
流是描述数据转换序列的一种方式。
使用Iterator,您可以获得一个数据值,处理它,然后获得另一个数据值。
使用Streams,您将一个函数序列链接在一起,然后向流提供一个输入值,并从组合的序列获得输出值。注意,在Java术语中,每个函数都封装在一个Stream实例中。Streams API允许您以链接转换表达式序列的方式链接Stream实例序列。
为了完成流的概念,您需要一个数据源来提供流,以及一个使用流的终端函数。
你向流中提供值的方式实际上可能来自一个Iterable,但流序列本身不是一个Iterable,它是一个复合函数。
流也被认为是懒惰的,在某种意义上,它只在你向它请求一个值时才工作。
请注意Streams的这些重要假设和特性:
A Stream in Java is a transformation engine, it transforms a data item in one state, to being in another state.
streams have no concept of the data order or position, the simply transform whatever they are asked to.
streams can be supplied with data from many sources, including other streams, Iterators, Iterables, Collections,
you cannot "reset" a stream, that would be like "reprogramming the transformation". Resetting the data source is probably what you want.
there is logically only 1 data item 'in flight' in the stream at any time (unless the stream is a parallel stream, at which point, there is 1 item per thread). This is independent of the data source which may have more than the current items 'ready' to be supplied to the stream, or the stream collector which may need to aggregate and reduce multiple values.
Streams can be unbound (infinite), limited only by the data source, or collector (which can be infinite too).
Streams are 'chainable', the output of filtering one stream, is another stream. Values input to and transformed by a stream can in turn be supplied to another stream which does a different transformation. The data, in its transformed state flows from one stream to the next. You do not need to intervene and pull the data from one stream and plug it in to the next.
c#的比较
当您认为Java流只是供应、流和收集系统的一部分,并且流和迭代器经常与集合一起使用时,那么就难怪很难将几乎全部嵌入到c#中的单个IEnumerable概念中的相同概念联系起来。
IEnumerable的某些部分(以及密切相关的概念)在所有Java Iterator、Iterable、Lambda和Stream概念中都很明显。
Java概念可以做的一些小事情在IEnumerable中比较困难,反之亦然。
结论
这里没有设计问题,只是语言之间的概念匹配问题。
流以不同的方式解决问题
流为Java添加了功能(它们添加了一种不同的做事方式,而不是将功能带走)
添加流可以让你在解决问题时有更多选择,这可以被公平地归类为“增强力量”,而不是“减少”、“带走”或“限制”。
为什么Java流是一次性的?
这个问题是错误的,因为流是函数序列,而不是数据。根据提供流的数据源,您可以重置数据源,并提供相同或不同的流。
不像c#的IEnumerable,一个执行管道可以被我们想执行多少次就执行多少次,在Java中一个流只能被“迭代”一次。
比较IEnumerable和Stream是错误的。你用来说明IEnumerable可以被你想执行多少次就可以执行多少次的上下文,与Java Iterables相比是最好的,后者可以被你想迭代多少次。Java流代表IEnumerable概念的子集,而不是提供数据的子集,因此不能“重新运行”。
任何对终端操作的调用都会关闭流,使其不可用。这个“功能”会带走很多功能。
从某种意义上说,第一个说法是正确的。“剥夺权力”的说法则不然。你仍然在比较Streams和IEnumerables。流中的终端操作类似于for循环中的“break”子句。如果您愿意,并且可以重新提供所需的数据,您总是可以自由地拥有另一个流。同样,如果你认为IEnumerable更像一个Iterable,对于这条语句,Java做得很好。
我想这不是技术上的原因。这种奇怪的限制背后的设计考虑是什么?
原因是技术上的,原因很简单,流是思想的子集。流子集不控制数据供应,因此您应该重置供应,而不是流。在这种情况下,这并不奇怪。
快速排序的例子
您的快速排序示例具有签名:
IEnumerable<int> QuickSort(IEnumerable<int> ints)
你将输入IEnumerable作为数据源:
IEnumerable<int> lt = ints.Where(i => i < pivot);
此外,返回值也是IEnumerable,这是一个数据供应,由于这是一个Sort操作,该供应的顺序很重要。如果你认为Java Iterable类是合适的匹配,特别是Iterable的List专门化,因为List是一个有保证顺序或迭代的数据供应,那么与你的代码等效的Java代码将是:
Stream<Integer> quickSort(List<Integer> ints) {
// Using a stream to access the data, instead of the simpler ints.isEmpty()
if (!ints.stream().findAny().isPresent()) {
return Stream.of();
}
// treating the ints as a data collection, just like the C#
final Integer pivot = ints.get(0);
// Using streams to get the two partitions
List<Integer> lt = ints.stream().filter(i -> i < pivot).collect(Collectors.toList());
List<Integer> gt = ints.stream().filter(i -> i > pivot).collect(Collectors.toList());
return Stream.concat(Stream.concat(quickSort(lt), Stream.of(pivot)),quickSort(gt));
}
注意有一个错误(我已经复制),在排序不处理重复值优雅,这是一个'唯一值'排序。
还要注意Java代码是如何在不同的地方使用数据源(List)和流概念的,而在c#中,这两种“个性”可以用IEnumerable来表示。此外,虽然我已经使用List作为基类型,但我可以使用更通用的Collection,并且通过一个小的迭代器到流的转换,我可以使用更通用的Iterable