为什么这个Java 8流示例不能编译?

Sti*_*ael 9 java type-inference java-8 java-stream

我想弄清楚为什么这段代码不能在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是编译器的错误:

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
Run Code Online (Sandbox Code Playgroud)

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

Hol*_*ger 10

正如Peter Lawrey指出的那样,? extends Example<?>是不兼容的E extends Example<E>.尽管如此,即使修复签名也不会使类型推断工作.

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

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,您必须重新考虑代码的语义.仅在返回类型中出现的方法的类型参数不能正确,因为它暗示" 无论调用者如何替换此类型参数,该方法都将返回正确的内容 ".由于方法实现不知道调用者假设什么,这是不可能的.只返回null或空列表才能正常工作,这没什么用处.

  • 然后,使用您传递的“Class&lt;E&gt;”类型的输入参数,类型推断也应该与链接的“.map(…).collect(…)”调用一起工作。 (2认同)

Pet*_*rey 9

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

? extends Example<?>
Run Code Online (Sandbox Code Playgroud)

作为匹配

E extends Example<E>
Run Code Online (Sandbox Code Playgroud)

因为它不能假设两者?是相同的.它可能是

A extends Example<B>
Run Code Online (Sandbox Code Playgroud)

执行强制转换时,会隐藏约束以使其匹配.