所以,我写了这个相对来说比较简单的代码,我和IntelliJ IDEA都看不出有什么问题,但是在标记的那一行上javac会崩溃,并抱怨它无法推断类型:
import java.util.List;
import java.util.stream.Collectors;
public class GenericsBreakJavac8 {
public interface Edge<N> {
N getNode();
}
@FunctionalInterface
public interface EdgeCreator<N, E extends Edge<N>> {
E createEdge(N node);
}
public static <N> List<Edge<N>> createEdges(List<N> nodes) {
return createEdges(nodes, DefaultEdge::new); //the deadly line
}
//THE NEWLY ADDED LINE (see the edit note)
public static <N> List<Edge<N>> createEdges2(List<N> nodes) {
return createEdges(nodes, n -> new DefaultEdge<N>(n));
}
public static <N, E extends Edge<N>> List<E> createEdges(List<N> nodes, EdgeCreator<N, E> edgeCreator) {
return nodes.stream().map(edgeCreator::createEdge).collect(Collectors.toList());
}
public static class DefaultEdge<N> implements Edge<N> {
private final N node;
public DefaultEdge(N node) {
this.node = node;
}
@Override
public N getNode() {
return node;
}
}
}
将有问题的代码分成两行并加上显式类型确实有帮助,但是类型定义比lambda本身要长,这完全违背了最初编写lambda的目的...
编辑: 如果我使用实际的lambda而不是方法引用,问题会再次出现。请参见上面新增的方法。
javac
在类型推断时不会考虑 lambda 函数体。它没有足够的信息来完全确定EdgeCreator
的类型签名。您可以使用方法引用,或者完全指定createEdges
的类型参数:GenericsBreakJava8.<N, Edge<N>>createEdges(nodes, node -> new DefaultEdge<>(node))
。 - JeffreyPair::first
代替Pair<String,Integer>::first
这样使用。类似地,像Pair::new
这样的方法引用被视为“钻石”实例创建(new Pair<>()
)。因为“钻石”是隐式的,所以这种形式不会实例化原始类型;事实上,没有办法表达对原始类型构造函数的引用。” - RadiodefDefaultEdge::new
称为隐式类型的方法引用,将DefaultEdge<N>::new
称为显式类型的方法引用,会使它更清晰明了。顺便说一下,您还可以使用显式类型的lambda表达式来解决问题:return createEdges(nodes, (N n) -> new DefaultEdge<>(n))
。 - Holger