深入了解分裂者的特征

Understanding deeply spliterator characteristics

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

Q1:Stream.empty() vs Stream.of()(Stream.of() 无参数)

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

Q2:LongStream.of()没有NONNULL

刚刚注意到 NONNULL 在 LongStream.of 中不可用。 NONNULL不就是所有LongStreamIntStreamDoubleStream的主要特征吗?

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

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

我明白 .mapToObj() 可以失去 NONNULL、IMMUTABLE 和 DISTICT,但是 .boxed()... 没有意义。

Q4:.peek()丢失IMMUTABLE和NONNULL

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

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

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

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

Q6: .filter() 丢失 IMMUTABLE, NONNULL

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


如果对拆分器有深刻理解的人能带来一些清晰度,我将不胜感激。谢谢。

我不得不承认,当我第一次尝试找出特征的实际含义时,我也遇到了困难,感觉在 Java 8 的实施阶段,它们的含义并没有明确确定,并且由于这个原因使用不一致。

考虑 Spliterator.IMMUTABLE:

Characteristic value signifying that the element source cannot be structurally modified; that is, elements cannot be added, replaced, or removed, so such changes cannot occur during traversal.

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

如果我们更慷慨地将其解释为“只要客户无法观察到”,则与 CONCURRENT 没有显着差异,因为在任何一种情况下 some元素将报告给客户端,但无法识别它们是在遍历期间添加的,还是由于删除而未报告的,因为无法倒带拆分器和比较。

规范继续:

A Spliterator that does not report IMMUTABLE or CONCURRENT is expected to have a documented policy (for example throwing ConcurrentModificationException) concerning structural interference detected during traversal.

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

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

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


同样,NONNULL 没有在任何地方使用,所以它对某些流没有影响。我可以将 LongStream.of(…) 问题追溯到 Arrays.spliterator(long[], int, int) 的内部使用,它委托给
Spliterators.spliterator​(long[] array, int fromIndex, int toIndex, int additionalCharacteristics):

The returned spliterator always reports the characteristics SIZED and SUBSIZED. The caller may provide additional characteristics for the spliterator to report. (For example, if it is known the array will not be further modified, specify IMMUTABLE; if the array data is considered to have an encounter order, specify ORDERED). The method Arrays.spliterator(long[], int, int) can often be used instead, which returns a spliterator that reports SIZED, SUBSIZED, IMMUTABLE, and ORDERED.

(再次)注意 IMMUTABLE 特性的不一致使用。它再次被视为必须保证没有任何修改,而与此同时,Arrays.spliterator 进而 Arrays.streamLongStream.of(…) 将报告 IMMUTABLE 特征,即使根据规范,不能保证调用者不会修改他们的数组。除非我们考虑将一个元素设置为不进行结构修改,但是那样的话,整个区别又变得毫无意义了,因为数组不能进行结构修改。

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

糟糕的是,如果不更改规范就无法解决这个问题,好在无论如何都没有后果。


因此,如果我们忽略 CONCURRENTIMMUTABLENONNULL 中没有任何后果的任何问题,我们有

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

合并 Stream.concat(…)Stream.empty()。空流 不会 对结果顺序施加约束,这听起来很合理。但是Stream.concat(…)只有一个input没有订单时才放订单的行为,值得商榷。请注意,在排序方面过于激进并不是什么新鲜事,请参阅 关于一种首先被认为是故意的行为,但后来已被修复,直到 Java 8,更新 60。也许,Stream.concat 也应该在这个时候讨论…

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