【问题标题】:Understanding deeply spliterator characteristics深入了解分裂器特征
【发布时间】:2018-03-20 13:17:22
【问题描述】:

为了尝试深入理解java流和拆分器,我有一些关于拆分器特性的微妙问题:

Q1:Stream.empty()Stream.of()(Stream.of() 不带参数)

  • Stream.empty()已缩小,已调整
  • Stream.of()SUBSIZED,IMMUTABLE,SIZED,ORDERED

为什么Stream.empty() 没有Stream.of() 的相同特征?请注意,与 Stream.concat() 结合使用时会产生影响(特别是没有ORDERED)。我想说Stream.empty() 不仅应该有 IMMUTABLE 和 ORDERED,还应该有 DISTINCT 和 NONNULLStream.of() 也有意义,只有一个参数具有 DISTICT

Q2:LongStream.of() 没有 NONNULL

刚刚注意到 NONNULL 在LongStream.of 中不可用。 NONNULL不是所有LongStreams、IntStreams和DoubleStreams的主要特征吗?

Q3:LongStream.range(,)LongStream.range(,).boxed()

  • LongRange.range(,): SUBSIZED, IMMUTABLE, NONNULL, SIZED, ORDERED, SORTED, DISTINCT
  • LongStream.range(,).boxed()已订制、已订制、已订制

为什么.boxed() 会失去所有这些特征?它不应该丢失任何东西。

我知道.mapToObj() 可以丢失NONNULL、IMMUTABLE 和DISTICT,但是.boxed()... 没有意义。

Q4:.peek() 丢失 IMMUTABLE 和 NONNULL

LongStream.of(1): SUBSIZED, IMMUTABLE, NONNULL, SIZED, ... LongStream.of(1).peek(): SUBSIZED, SIZED, ...

为什么.peek() 会失去这些特征? .peek 不应该真的失去任何东西。

Q5:.skip().limit() 丢失SUBSIZED、IMMUTABLE、NONNULL、SIZED

请注意,这些操作会丢失SUBSIZED、IMMUTABLE、NONNULL、SIZED。为什么?如果尺寸可用,那么最终尺寸也很容易计算出来。

Q6:.filter() 丢失IMMUTABLE,NONNULL

请注意,此操作也会丢失SUBSIZED、IMMUTABLE、NONNULL、SIZED。丢失 SUBSIZED 和 SIZED 是有意义的,但其他两个没有意义。为什么?


如果有人对拆分器有深入的了解,我将不胜感激。谢谢。

【问题讨论】:

  • 有人投票以“太宽泛”结束这个问题,我也很想这样做,因为它看起来像是六个问题合二为一。另一方面,可能对所有这些细节都有一个全面的解释......
  • @Nicolai 我最初投了票……但它太好了,不能关闭,所以我收回了它。对于这么好的问题,我喜欢这个“一体式”。除了一些 Oracle 用户(Stuart?或 Brian?),我只希望 Holger 知道这些用户的确切本质。热切地等待着。 :)
  • 我了解.mapToObj() 可能会丢失 NONNULL、IMMUTABLE 和 DISTI[N]CT。为什么不排序?
  • @shmosel 好点!没有想到它可能会破坏排序。不错!

标签: java java-8 java-stream spliterator


【解决方案1】:

我不得不承认,当我第一次尝试找出特性的实际含义时,我也遇到了困难,并且感觉它们的含义在 Java 8 的实现阶段并没有明确地确定,因此使用不一致.

考虑Spliterator.IMMUTABLE

表示元素源不能被结构修改的特征值;即元素不能被添加、替换或删除,因此在遍历过程中不会发生这种变化。

在这个列表中看到“替换”很奇怪,当谈到 List 或数组时,通常不将其视为结构修改,因此,接受数组(未克隆)报告的流和拆分器工厂报告 @ 987654328@,如LongStream.of(…)Arrays.spliterator(long[])

如果我们更宽泛地将此解释为“只要客户端无法观察到”,则与 CONCURRENT 没有显着差异,因为在任何一种情况下,一些元素都会报告给客户端没有任何方法可以识别它们是在遍历过程中添加的,还是由于删除而未报告的,因为没有办法回退拆分器并进行比较。

规范继续:

不报告IMMUTABLECONCURRENT 的Spliterator 应具有关于在遍历期间检测到的结构干扰的书面政策(例如抛出ConcurrentModificationException)。

这是唯一相关的事情,报告IMMUTABLECONCURRENT 的拆分器保证永远不会抛出ConcurrentModificationException。当然,CONCURRENT 在语义上排除了SIZED,但这对客户端代码没有影响。

事实上,这些特性并没有用于 Stream API 中的任何内容,因此,不一致地使用它们永远不会在某处引起注意。

这也解释了为什么每个中间操作都有清除CONCURRENTIMMUTABLENONNULL特征的效果:Stream实现不使用它们及其内部类表示流状态不维护它们。


同样,NONNULL 不会在任何地方使用,因此对于某些流来说,它的缺失没有任何影响。我可以将LongStream.of(…) 问题追溯到Arrays.spliterator(long[], int, int) 的内部使用,它代表
Spliterators.spliterator​(long[] array, int fromIndex, int toIndex, int additionalCharacteristics):

返回的拆分器始终报告特征SIZEDSUBSIZED。调用者可以为拆分器提供额外的特性来报告。 (例如,如果已知数组不会被进一步修改,则指定IMMUTABLE;如果认为数组数据有遇到顺序,则指定ORDERED)。通常可以使用Arrays.spliterator(long[], int, int) 方法来代替,它返回一个拆分器,报告SIZEDSUBSIZEDIMMUTABLEORDERED

注意(再次)IMMUTABLE 特性的不一致使用。它再次被视为必须保证没有任何修改,同时Arrays.spliteratorArrays.streamLongStream.of(…) 将报告IMMUTABLE 特征,即使按照规范,也无法保证调用者不会修改他们的数组。除非我们考虑将元素设置为不进行结构修改,否则整个区别再次变得毫无意义,因为数组不能进行结构修改。

它明确指定没有NONNULLcharacteristic。虽然很明显原始值不能是null,并且Spliterator.Abstract<Primitive>Spliterator 类总是注入NONNULL 特征,但Spliterators.spliterator​(long[],int,int,int) 返回的拆分器不会继承自Spliterator.AbstractLongSpliterator

不好的是,不改变规范是无法解决的,好的是,无论如何它没有任何后果。


因此,如果我们忽略 CONCURRENTIMMUTABLENONNULL 的任何问题,而这些问题不会产生任何后果,那么我们有

SIZEDskiplimit。这是一个众所周知的问题,是由 Stream API 实现的skiplimit 方式的结果。其他实现是可以想象的。这也适用于无限流与limit 的组合,它应该具有可预测的大小,但鉴于当前的实现,还没有。

Stream.concat(…)Stream.empty() 组合在一起。空流对结果顺序施加约束听起来很合理。但是Stream.concat(…)在只有一个输入没有顺序的情况下释放顺序的行为值得怀疑。请注意,在订购方面过于激进并不是什么新鲜事,请参阅 this Q&A 关于首先被认为是故意的行为,但后来在 Java 8 更新 60 中得到了修复。也许,Stream.concat 应该在此讨论时间点也是……

.boxed() 的行为很容易解释。当它像.mapToObj(Long::valueOf) 一样天真地实现时,它会简单地丢失所有知识,因为mapToObj 不能假设结果仍然是排序的或不同的。但这已在 Java 9 中得到解决。在那里,LongStream.range(0,10).boxed() 具有 SUBSIZED|SIZED|ORDERED|SORTED|DISTINCT 特征,保留了与实现相关的所有特征。

【讨论】:

猜你喜欢
  • 2020-12-10
  • 1970-01-01
  • 2020-04-13
  • 1970-01-01
  • 1970-01-01
  • 2012-01-17
  • 2020-05-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多