为什么这里类型推断失败了?

8

所以,我写了这个相对来说比较简单的代码,我和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而不是方法引用,问题会再次出现。请参见上面新增的方法。
2个回答

8

javac 不支持 DefaultEdge 是原始类型。

    return createEdges(nodes, DefaultEdge<N>::new);

将按预期工作。


2
@kaqqao javac 在类型推断时不会考虑 lambda 函数体。它没有足够的信息来完全确定 EdgeCreator 的类型签名。您可以使用方法引用,或者完全指定 createEdges 的类型参数:GenericsBreakJava8.<N, Edge<N>>createEdges(nodes, node -> new DefaultEdge<>(node)) - Jeffrey
我明白了,感谢您的详细说明! - kaqqao
3
实际上,这不被视为原始类型:“为了方便起见,当使用泛型类型的名称引用实例方法(其中接收器成为第一个参数)时,目标类型用于确定类型参数。这使得可以像 Pair::first 代替 Pair<String,Integer>::first 这样使用。类似地,像 Pair::new 这样的方法引用被视为“钻石”实例创建(new Pair<>())。因为“钻石”是隐式的,所以这种形式不会实例化原始类型;事实上,没有办法表达对原始类型构造函数的引用。” - Radiodef
2
该引用的来源是 https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13.1,在未标记的示例下。 - Radiodef
6
是的,规范确实是以那种方式写的,但那是一个糟糕的决定。将其称为原始类型会与“真正的原始类型”产生混淆,以至于早期的Java 8编译器在方法引用时生成原始类型警告。即使规范使用了这个措辞,我也会避免使用它。将DefaultEdge::new称为隐式类型的方法引用,将DefaultEdge<N>::new称为显式类型的方法引用,会使它更清晰明了。顺便说一下,您还可以使用显式类型的lambda表达式来解决问题:return createEdges(nodes, (N n) -> new DefaultEdge<>(n)) - Holger
显示剩余2条评论

4
除了 Jeffrey's answer之外,注意你的EdgeCreator接口在功能上与Function没有区别,而createEdges实际上不需要有问题的类型约束。因此,一种解决方法是让EdgeCreator扩展Function以简化createEdges
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public class GenericsBreakJavac8 {

    public interface Edge<N> {
        N getNode();
    }

    @FunctionalInterface
    public interface EdgeCreator<N, E extends Edge<N>> extends Function<N,E> {
        E createEdge(N node);
        @Override public default E apply(N t) { return createEdge(t); }
    }

    public static <N> List<Edge<N>> createEdges(List<N> nodes) {
        return createEdges(nodes, DefaultEdge::new);
    }

    public static <N> List<Edge<N>> createEdges2(List<N> nodes) {
        return createEdges(nodes, n -> new DefaultEdge<>(n));
    }

    public static <T,R> List<R> createEdges(List<T> nodes, Function<T,R> edgeCreator) {
        return nodes.stream().map(edgeCreator).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;
        }
    }
}

您还可以删除EdgeCreator接口,并在需要强制执行约束的地方使用Function<N,E extends Edge<N>>,但是在此代码中,没有这样的地方。当有人尝试在需要List<Edge<SpecificNodeType>>的上下文中使用createEdges(List,Function)或其结果时,仍将强制执行约束。

在太多的代码位置上保持类型约束只会使您的源代码膨胀而没有任何好处。

顺便说一句,在您当前的代码中,甚至不需要DefaultEdge类;您可以简化整个代码为

import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public class GenericsBreakJavac8 {

    public interface Edge<N> {
        N getNode();
    }

    public static <N> List<Edge<N>> createEdges(List<N> nodes) {
        return createEdges(nodes, n -> () -> n);
    }

    public static <T,R> List<R> createEdges(List<T> nodes, Function<T,R> edgeCreator) {
        return nodes.stream().map(edgeCreator).collect(Collectors.toList());
    }
}

感谢您的好回答!确实这里有很多冗余,但这只是因为我无情地删除了代码以制作这个示例...我的真实代码有更多的部分。 - kaqqao

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接