【问题标题】:Why doesn't this Java 8 stream example compile?为什么这个 Java 8 流示例无法编译?
【发布时间】:2015-06-02 14:55:23
【问题描述】:

我试图弄清楚为什么这段代码不能在 JDK 1.8.0_45 上编译:

public class Example<E extends Example<E>> {
    public List<? extends Example<?>> toExamples(Collection<String> collection) {
        return collection.stream()
                .map(v -> lookup(v))
                .collect(Collectors.toList());
    }

    public static <E extends Example<E>> E lookup(String value) {
        return null;
    }
}

添加一个看似不必要的演员表修复它:

public class Example<E extends Example<E>> {
    public List<? extends Example<?>> toExamples(Collection<String> collection) {
        return collection.stream()
                .map(v -> (Example<?>) lookup(v))
                .collect(Collectors.toList());
    }

    public static <E extends Example<E>> E lookup(String value) {
        return null;
    }
}

这是编译器的错误:

Example.java:9: error: incompatible types: inference variable R has incompatible bounds
              .collect(Collectors.toList());
                      ^
  equality constraints: List<Object>
  upper bounds: List<? extends Example<?>>,Object
where R,A,T are type-variables:
  R extends Object declared in method <R,A>collect(Collector<? super T,A,R>)
  A extends Object declared in method <R,A>collect(Collector<? super T,A,R>)
  T extends Object declared in interface Stream

由于某种原因,lookup() 的返回类型未正确推断为扩展 Example 的内容。

【问题讨论】:

  • 出于好奇,它是否可以在任何以前的 Java 8 版本上编译?这份报告似乎非常相关:bugs.openjdk.java.net/browse/JDK-8077304
  • 您得到的确切错误信息是什么?
  • 也可以更改lookup的签名:public static &lt;E extends Example&lt;E&gt;&gt; Example&lt;E&gt; lookup(String value)
  • 又一个泛型方法的实例,声称返回调用者希望的任何东西......

标签: java java-8 type-inference java-stream


【解决方案1】:

作为Peter Lawrey pointed out? extends Example&lt;?&gt;E extends Example&lt;E&gt; 不兼容。尽管如此,即使修复签名也不能使类型推断在这里起作用。

原因是类型推断的一个已知限制,因为它不会通过链式方法调用进行反向传播。换句话说,返回类型允许推断collect(…) 调用的类型,但不能推断前面的map(…) 调用的类型。 (另见this answer

但它适用于嵌套方法调用,因此可以编译以下重写方法:

public class Example<E extends Example<E>> {
    public <E extends Example<E>> List<E> toExamples(Collection<String> collection) {
        return collection.stream()
            .collect(Collectors.mapping(v -> lookup(v), Collectors.toList()));
    }

    public static <E extends Example<E>> E lookup(String value) {
        return null;
    }
}

不过,您必须重新考虑代码的语义。仅出现在返回类型中的方法的类型参数不可能是正确的,因为它暗示“无论调用者替换此类型参数,方法都会返回正确的东西”。由于方法实现不知道调用者假设什么,这是不可能的。只有返回null或者空列表才能正常工作,用处不大。

【讨论】:

  • 谢谢,我可以通过这种方式修复它。请注意,实际问题在toExamples() 方法中有一个Class&lt;? extends Example&lt;?&gt;&gt; 参数,在lookup() 方法中有一个Class&lt;E&gt; 参数。所以恐怕我把问题简单化了。
  • 然后,使用您传递的Class&lt;E&gt; 类型的输入参数,类型推断也应该与链式.map(…).collect(…) 调用一起使用。
【解决方案2】:

当您有 ? 时,它不等于另一个 ?,即编译器看不到

? extends Example<?>

作为匹配

E extends Example<E>

因为它不能假设两个? 是相同的。可能是

A extends Example<B>

当你执行强制转换时,你隐藏了约束以便它可以匹配。

【讨论】:

    【解决方案3】:

    我的猜测是静态方法中定义的泛型类型与类中定义的泛型类型不一样。您应该能够使 lookup 方法成为非静态方法,以便它与类级别泛型声明中定义的相同类型匹配:

        public E lookup(String value) {
            return null;
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-05-29
      • 2016-04-17
      • 2016-10-31
      • 2014-04-28
      • 2015-06-07
      • 1970-01-01
      相关资源
      最近更新 更多