【问题标题】:Why does type inference fail here?为什么类型推断在这里失败?
【发布时间】:2018-01-30 00:35:38
【问题描述】:

所以,我编写了这个相对简单的代码,我和 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;
        }
    }
}

使用显式类型将有问题的行分成 2 行会有所帮助,但类型签名比 lambda 长,完全违背了最初编写 lambda 的目的......

编辑: 如果我使用实际的 lambda 而不是方法引用,我会再次遇到问题。见上面新增的方法。

【问题讨论】:

    标签: java generics intellij-idea java-8


    【解决方案1】:

    javac 不喜欢 DefaultEdge 是原始类型。

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

    将按预期工作。

    【讨论】:

    • 你说得对……但我最初解释的更糟。如果可以,请查看我的编辑。
    • @kaqqao javac 在类型推断期间不考虑 lambda 体。它没有足够的信息来完全确定EdgeCreator 的类型签名。您可以使用方法引用,也可以将类型参数完全指定为createEdgesGenericsBreakJava8.&lt;N, Edge&lt;N&gt;&gt;createEdges(nodes, node -&gt; new DefaultEdge&lt;&gt;(node))
    • 这实际上不被认为是原始类型:"为方便起见,当使用泛型类型的名称来引用实例方法时(其中接收者成为第一个参数),目标类型用于确定类型参数。这有助于使用 Pair::first 代替 Pair&lt;String,Integer&gt;::first。类似地,像 Pair::new 这样的方法引用被视为“钻石”实例创建 (new Pair&lt;&gt;())。因为“钻石”是隐含的,这种形式不实例化原始类型;实际上,没有办法表示对原始类型的构造函数的引用。”
    • 引用的来源是docs.oracle.com/javase/specs/jls/se8/html/…,在未标记的示例下。
    • 是的,规范是这样写的,但这是一个糟糕的决定。将其称为原始类型会与“真正的原始类型”混淆,甚至早期的 Java 8 编译器也会在方法引用处生成原始类型警告。即使规范使用该措辞,我也会避免这种情况。将DefaultEdge::new 称为隐式类型的方法引用,将DefaultEdge&lt;N&gt;::new 称为显式类型的方法引用,会更清楚。顺便说一句,您也可以使用显式类型的 lambda 表达式来解决问题:return createEdges(nodes, (N n) -&gt; new DefaultEdge&lt;&gt;(n))
    【解决方案2】:

    除了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&lt;N, E extends Edge&lt;N&gt;&gt;,但是,在此代码中,没有这样的地方。当有人尝试使用 createEdges(List,Function) 时,约束仍然会被强制执行。它的结果,在需要 List&lt;Edge&lt;SpecificNodeType&gt;&gt; 的上下文中。

    在过多的代码位置携带类型约束只会使您的源代码膨胀而没有任何好处。

    顺便说一句,在您当前的代码中,您甚至不需要 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());
        }
    }
    

    【讨论】:

    • 感谢您的精彩回答!确实这里有很多冗余,但这只是因为我无情地删除了代码来制作这个例子......我的真实代码有更多的片段。
    猜你喜欢
    • 2011-03-19
    • 2017-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多