【问题标题】:Why does Collections.UnmodifiableMap.UnmodifiableEntrySet override the stream() method?为什么 Collections.UnmodifiableMap.UnmodifiableEntrySet 会覆盖 stream() 方法?
【发布时间】:2021-05-17 16:36:23
【问题描述】:

查看源代码时,我可以看到stream() 方法已在Collections.UnmodifiableMap.UnmodifiableEntrySet 中被覆盖。但代码似乎与Collection.stream() 相同,除了Collections.UnmodifiableMap.UnmodifiableEntrySet.stream() 中的返回类型更具体为Stream<Entry<K,V>> 而不仅仅是Stream<E>,如Collection.stream()。

spliterator() 方法在两个类中是不同的,但即使 stream 未被覆盖,我认为如果对象的类型为 UnmodifiableEntrySet,UnmodifiableEntrySet.spliterator() 将从 Collection.stream() 调用。

那么,stream 方法被覆盖有什么原因吗?

Collection.java

@Override
default Spliterator<E> spliterator() {
    return Spliterators.spliterator(this, 0);
}
 
default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

Collections.UnmodifiableMap.UnmodifiableEntrySet.java

@SuppressWarnings("unchecked")
public Spliterator<Entry<K,V>> spliterator() {
    return new UnmodifiableEntrySetSpliterator<>(
        (Spliterator<Map.Entry<K, V>>) c.spliterator());
}

@Override
public Stream<Entry<K,V>> stream() {
    return StreamSupport.stream(spliterator(), false);
}

【问题讨论】:

  • 只是因为UnmodifiableEntrySet 是一组Entry 而Collection 是通用的。我不确定是什么让您感到困惑。
  • @AniketSahrawat 是的。我的问题是,如果方法主体与 Collection.stream()
  • @AniketSahrawat 经过一些调试,我在某种程度上找到了解决方案。
  • @GauthamM - Stream&lt;Entry&lt;K,V&gt;&gt; 不是 Stream&lt;E&gt;。另一个例子是List&lt;String&gt; 不是List&lt;Object&gt;。
  • 我昨天误读了你的问题。如果您查看stream 的实现,您会注意到它已在 7 个地方被覆盖。相关覆盖位于UnmodifiableCollection 中,然后又位于UnmodifiableEntrySet 中。 UnmodifiableCollection#stream 基本上将调用委托给任何通过的Collection。如果UnmodifiableEntrySet 中没有覆盖,则调用将委托给原始Collection(可变)。

标签: java oop collections java-stream overriding


【解决方案1】:

在将Collections.java 的内容复制到新类CollectionsCopy.java 后,我尝试从UnmodifiableEntrySet 中删除stream 方法,然后我进行了一些调试并能够得到答案。

UnmodifiableEntrySet 扩展 UnmodifiableSet 扩展 UnmodifiableCollection 进而实现 Collection。由于UnmodifiableCollection 有一个字段final Collection&lt;? extends E&gt; c;,它必须覆盖stream(),如下所示

public Stream<E> stream() {
    return (Stream<E>)c.stream();
}

在UnmodifiableEntrySet 的构造函数中,Set&lt;? extends Map.Entry&lt;? extends K, ? extends V&gt;&gt; 对象被强制转换为原始Set 类型并传递给super 构造函数。

super((Set)s);

附加到代码的注释是:

需要转换为 raw 以解决类型的限制 系统

因此,当我从UnmodifiableEntrySet(在我的CollectionsCopy.java 中)删除stream() 时,由于原始类型转换,被调用的spliterator 方法是Set 中的方法,而不是@ 中的方法987654343@。这是因为UnmodifiableCollection 中的字段c 将是Set 而不是UnmodifiableEntrySet,并且当从UnmodifiableCollection.stream() 调用c.stream() 时,它将调用Set.spliterator()。因此,UnmodifiableEntrySet 有必要覆盖 stream() 的实现 UnmodifiableCollection。

另外,我尝试从UnmodifiableCollection 中删除覆盖的stream()。这一次,它按预期工作,因为 spliterator 是从 UnmodifiableEntrySet 调用的。

编辑: 基于一些 cmets 表明从Collection 返回的流不会是Entry 的流,而只是Stream。但在这种情况下,我不应该能够从流上的Entry 类调用任何方法。但我能够在该流上调用map(Entry::getValue)。所以,返回的流确实是Entry类型的流。

【讨论】:

    【解决方案2】:

    以下Java Doc/程序来自openjdk 14 2020-03-17。

    覆盖spliterator和stream的主要原因是确保UnmodifiableEntrySet的条目未被修改。

    来自UnmodifiableEntrySet的评论:

    除了 UnmodifiableSet 之外,我们还需要这个类,因为 Map.Entries 本身允许通过其 setValue 操作修改支持 Map。这个类很微妙:有许多可能的攻击必须被阻止。

    首先,UnmodifiableEntrySet 扩展 UnmodifiableSet 扩展 UnmodifiableCollection。 在UnmodifiableCollection中,使用代理模式来避免修改支持Collectionc,大多数方法只是调用支持Collection方法,如spliterator和stream:

        @Override
        public Spliterator<E> spliterator() {
            return (Spliterator<E>)c.spliterator();
        }
    
        @SuppressWarnings("unchecked")
        @Override
        public Stream<E> stream() {
            return (Stream<E>)c.stream();
        }
    

    因此,如果UnmodifiableEntrySet 不覆盖这些方法,则行为将遵循UnmodifiableCollection 实现,并且支持条目将被公开并可以通过Entry#setValue 进行修改。

    因此spliterator 和stream 方法被覆盖,并引入UnmodifiableEntrySetSpliterator 以使用UnmodifiableEntry 包装对支持条目的所有访问,确保该条目不能被修改。

    为什么UnmodifiableCollection 会覆盖stream?

    似乎没有必要在UnmodifiableCollection 中覆盖stream,因为我们可以在Collection 中使用默认实现(只需通过spliterator 创建流)。 但是作者决定使用支持Collection cstream 方法覆盖stream,可能的原因之一是支持Collection 可能出于性能原因覆盖stream 方法,例如Collections.CopiesList,或者是spliterator方法不符合Collection#stream的要求

    当 spliterator() 方法无法返回 IMMUTABLE、CONCURRENT 或后期绑定的拆分器时,应重写此方法。 (详见 spliterator()。)

    【讨论】:

    • 这解释了大部分。但我几乎没有怀疑。 stackoverflow.com/a/66198058/7804477 - 这是我尝试过的。在我的CollectionsCopy 类中,我从UnmodifiableEntrySet 和UnmodifiableCollection 中删除了覆盖的stream。然后我尝试了unmodifiableMap.entrySet().stream().findFirst().orElse(null)。这实际上返回了一个不可变的条目。如果我没有从UnmodifiableCollection 中删除覆盖的stream,那么可变条目会按预期返回。在UnmodifiableCollection 中确实需要覆盖stream。覆盖spliterator 还不够吗?
    • @Gautham M,我同意这并不是真正需要的。但是作者决定默认使用支持Collection cstream方法,我认为原因之一是c可能出于性能原因覆盖stream方法,例如Collections.CopiesList.
    • @GauthamM 我相信 samabcde 的回答是正确的,实际上需要重写 UnmodifiableEntrySet.stream()。我不确定为什么您的示例不起作用。我修改了我的 JDK 构建以删除该覆盖,并且流包含可变条目,因此允许修改不可修改的映射。
    • @StuartMarks 是的,如果我没有删除 UnmodifiableCollection 中被覆盖的 stream,我能够获得可变条目。如果我也删除了它,那么该条目将是不可变的。
    • @GauthamM 哦,是的,我现在明白你的问题了。没有重写 UnmodifiableCollection.stream 可能会逃脱,但它可能会使代码在维护时变得脆弱。包装器集合的一般规则是它们应该覆盖 everything 以便可以验证它们以保护它们的不变量,而无需检查整个类层次结构。
    猜你喜欢
    • 2017-08-23
    • 2011-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多