使用通配符调用泛型方法无法编译

3
有人可以解释一下为什么以下代码片段无法编译吗?
public class Main {

    public static void main(String[] args) {

        Integer[] integers = {3, 5, 8};
        Set<Integer> s1 = new HashSet<Integer>(Arrays.asList(integers));

        Double[] doubles = {3.5, 5.5, 8.5};
        Set<Double> s2 = new HashSet<Double>(Arrays.asList(doubles));

        Set<Number> res1 = union(s1, s1);       // ->it does not compile 
        Set<Number> res2 = union(s1, s2);       // ->it does not compile
    }

    static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2)
    {
        Set<E> result = new HashSet<E>(s1);
        result.addAll(s2);
        return result;
    }

}

我已经阅读了错误信息:

Type mismatch: cannot convert from Set<Integer> to Set<Number>  Main.java   /TestingGenerics/src/com/al/testinggenerics line 17 Java Problem

Type mismatch: cannot convert from Set<Number&Comparable<?>> to Set<Number> Main.java   /TestingGenerics/src/com/al/testinggenerics line 18 Java Problem

只要IntegerDouble继承自Number,这个问题是什么原因引起的?

事实上,@user3580294已经给出了答案:Java 7的类型推断并不够智能,无法在其类型解析中涉及目标类型。这在http://openjdk.java.net/jeps/101中得到了解决,而它在Java 8中的工作被称为“广义目标类型推断”-另请参见http://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html#target_types。 - Marco13
2个回答

3

编辑:答案错误。我误解了。

很奇怪,在Java 8中我没有收到编译错误。你可能需要直接告诉编译器E的类型。尝试:

Set<Number> res1 = Main.<Number> union(s1, s1);
Set<Number> res2 = Main.<Number> union(s1, s2);

我使用Java 7编译器,这对我有效。

我的猜测是,以前版本的Java编译器无法智能推断使表达式工作所需的正确类型,因此你被强制显式地告诉它。


1
当然。但是为什么参数E没有被推断为上下文中的Number类型呢?在这种情况下,我们实际上有Set<Number> result = new HashSet<Number>(s1); result.addAll(s2);,这应该没问题。 - Oliver Charlesworth
你说得对,我误解了问题。已经修复了。我想是这样。 - awksp
是的,我知道上面的代码可以工作。然而,如果没有明确定义要返回的类型,这看起来很奇怪,因为编译器无法推断类型,从而导致编译错误。 - arjacsoh
这是Java 8之前编译器的一个失败。在Java 8之前,为什么无法推断上述显式边界,您需要问作者。 - awksp

3
正如用户3580294 (+1) 和我在我的评论中提到的那样:原因是Java 7的类型推断不够智能。
但是,您可以通过省略通配符来超越类型推断,同时保持所需的灵活性:
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class TargetTypeInferenceTest
{
    public static void main(String[] args)
    {

        Integer[] integers = { 3, 5, 8 };
        Set<Integer> s1 = new HashSet<Integer>(Arrays.asList(integers));

        Double[] doubles = { 3.5, 5.5, 8.5 };
        Set<Double> s2 = new HashSet<Double>(Arrays.asList(doubles));

        Set<Number> res1 = union(s1, s1); // ->it does compile ;-)
        Set<Number> res2 = union(s1, s2); // ->it does compile ;-)
    }

    static <E, F extends E, G extends E> Set<E> union(Set<F> s1, Set<G> s2)
    {
        Set<E> result = new HashSet<E>(s1);
        result.addAll(s2);
        return result;
    }
}

通配符导致编译器放弃编译,而这个则没有,这真是相当有趣。不知道是什么导致了这种差异…… - awksp
@user3580294,我也非常想知道这个问题的答案,因为我正在努力熟悉(并实现)类型推断在子类型和泛型中的存在。但是当我说:我无法解释(目前)为什么这样可行,因为答案隐藏在http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2.7中时,我可能不必感到羞愧。在StackOverflow上可以找到一些相关问题,也许你会从中获得一些见解。 - Marco13

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