【问题标题】:Cannot return Stream of subclass objects after filtering过滤后无法返回子类对象的Stream
【发布时间】:2020-09-24 23:51:22
【问题描述】:

我有一个返回Stream 类型为A 的方法。我还有A 的子类型B。该方法创建了一个Stream,它填充了B 的实例,由于继承,这些实例也是A 类型。这很好用,直到我在Stream 上引入一个过滤器。然后,编译器决定 Stream 的类型是 B 而不是 A,继承似乎无关紧要。为什么会这样?

这是一个最小的可重现示例:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class Main {

    abstract class A {
        int member;
    }

    class B extends A {

        public B(int member) {
            this.member = member;
        }
    }

    public static void main(String[] args) {
        Stream<A> stream = getStream();
    }

    private Stream<A> getStream() {
        List<Integer> ints = new ArrayList<>();
        ints.add(1);
        ints.add(2);
        ints.add(3);

        return ints.stream().map(B::new).filter(b -> true); // Filter causes compiler to throw error
    }

}

【问题讨论】:

  • 只需在方法getStream()中返回Stream&lt;? extends A&gt;
  • 谢谢,这似乎是一个很好的解决方案。想知道为什么我不能做我正在做的事情。
  • 但是过滤器怎么会破坏一切呢?在我使用过滤器之前它工作正常。过滤器是否将Stream&lt;A&gt; 转换为Stream&lt;B&gt;
  • 对了,没看到。好吧,那我只有一个不满意的答案:因为 Java 的类型推断加上泛型是疯了。就像,严重疯了。这可能与map 接受协变参数而filter 接受变体参数这一事实有关。编辑:不再是这个兔子洞了...书签,如果我想出什么我会回到这里。

标签: java lambda java-stream type-inference


【解决方案1】:

感谢 Holger,为您澄清事实。这是泛型类型的类型推断没有扩展到chained invocation 的情况,因为它会带来额外的复杂性。因此,一旦您链接 filter() 调用,推理机制就无法再自动确定类型。

除了在 map() 调用中指定泛型类型之外,您无需将额外的笨拙 .map(A.class::cast)modify the return type 转换为 Stream&lt;? extends A&gt; 或执行任何其他操作

return ints.stream().<A>map(B::new).filter(b -> true);

【讨论】:

  • 通过编译器在Stream&lt;A&gt; foo = ints.stream().map(B::new)中的推断,我们还能说Stream是不变的吗?在我看来是协变的。
  • 我们仍然会说generics are invariant,因为这是事实。我们不能说“流是不变的”,因为Stream 是一个对象。在这种情况下,调用点协方差通过&lt;? extends R&gt; 启用,但R 的泛型类型仍然不变。
  • 在这种情况下,map 的方差无关紧要,相反,您会看到类型推断在起作用。对于Stream&lt;A&gt; foo = ints.stream().map(B::new);,目标类型Stream&lt;A&gt; 将使Java 为方法引用B::new 推断Function&lt;Integer,A&gt;。对于另一个用例,.map(B::new).filter(b -&gt; true)map 没有目标类型,可以进行相同的推理。当您使用.&lt;A&gt;map(B::new) 时,您将再次拥有一个目标类型。不需要类型转换。
  • 问题是类型推断不适用于链式方法调用。对于return ints.stream().map(B::new).filter(b -&gt; true);,该方法的返回类型为filter 调用提供目标类型,但不为调用filter 的表达式提供目标类型。如果签名中没有通配符类型,则没有区别,因为当您使用 lambda 表达式时,通配符完全无关紧要。差异仅与具有独立类型的参数表达式相关,即对预先存在的 Function 对象的引用。
  • 作为附录,作为建议返回 Stream&lt;? extends A&gt; 的评论,the Guidelines for Wildcard Use 不鼓励返回带有通配符的类型:“应避免使用通配符作为返回类型,因为它会强制程序员使用代码来处理通配符”。
【解决方案2】:

(对评论做出反应,但太长了)

filter() 方法不会将Stream&lt;A&gt; 转换为Stream&lt;B&gt;,实际上map() 返回的东西可以分配给Stream&lt;A&gt;Stream&lt;B&gt;(自己试试——将映射重构为局部变量)。推理机制很复杂,并且还考虑了目标类型(即重构的局部变量或 - 就像在您的原始示例中一样 - 方法返回类型,在这种情况下,流程被 filter 方法破坏)。

您可以像 Alex 所写的那样将返回类型更改为 Stream&lt;? extends A&gt;,或者在原始 mapfilter 之间添加显式向上转换:.map(A.class::cast)。坦率地说,我现在看不到任何提议的意义。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-10-07
    • 1970-01-01
    • 2021-10-02
    • 1970-01-01
    • 1970-01-01
    • 2020-12-07
    • 1970-01-01
    相关资源
    最近更新 更多