具有较低有界类型的Java类型推断

时间:2014-12-26 04:34:52

标签: java generics type-inference bounded-wildcard

为什么Java可以推断多个上限类型的共同祖先,而不是下限类型?

更具体地说,请考虑以下示例:

static class Test {

    static <T> T pick(T one, T two) {
        return two;
    }

    static void testUpperBound() {
        List<? extends Integer> extendsInteger = new ArrayList<>();

        // List<? extends Integer> is treated as a subclass of List<? extends Number>
        List<? extends Number> extendsNumber = extendsInteger;

        // List<? extends Number> is inferred as the common superclass
        extendsNumber = pick(extendsInteger, extendsNumber);
    }

    static void testLowerBound() {
        List<? super Number> superNumber = new ArrayList<>();

        // List<? super Number> is treated as a subclass of List<? super Integer>
        List<? super Integer> superInteger = superNumber;

        // The inferred common type should be List<? super Integer>,
        // but instead we get a compile error:
        superInteger = pick(superNumber, superInteger);

        // It only compiles with an explicit type argument:
        superInteger = Test.<List<? super Integer>>pick(superNumber, superInteger);
    }
}

2 个答案:

答案 0 :(得分:2)

我想我可以解释为什么Java可以区分下限和上限类型。

当使用不兼容的边界时,尝试推断公共下限可能会失败,例如IntegerLong。当我们使用上限时,总是可以找到一些共同的上限,在这种情况下List<? extends Number>。但List<? super Integer>List<? super Long>没有共同的下限。如果发生此类冲突,唯一安全的选择是返回List<? extends Object>,与List<?>同义,意思是“List未知类型”。

现在,可以说,只有在实际存在冲突边界的情况下,我才能采取这种做法,而不是我的问题中的情况。但也许决定采取简单的方法,除非明确指定,否则不要假设有一个共同的下限。

答案 1 :(得分:0)

我使用1.8.0_25并且我收到了编译错误。 但是,错误并不是对pick的调用很糟糕,而是要将结果放入的变量。 重复你的例子:

static void testLowerBound() {
    List<? super Number> superNumber = new ArrayList<>();
    List<? super Integer> superInteger = superNumber;

    // this gets the error
    superInteger = pick(superNumber, superInteger);
    // this doesn't
    pick(superNumber, superInteger);

    // what's happening behind is
    List<? extends Object> behind = pick(superNumber, superInteger);
    superInteger = behind;
    // that last line gets the same compilation error
}

如果你看看在调用中如何替换T,参数将被用作List,丢失有关下限的信息。

关于推论:每一个?不完全是&#34;无论可以分配给什么......&#34;但是&#34;我想要命名的特定类型,可以分配给...&#34;。这很重要,因为在你的例子中,你得到3个变量,每个列表1个,另一个,不同的,变量的结果。 现在,由于pick的声明,T的替换必须满足参数的类层次结构。在第一种情况下,您需要替换&lt;#1 extends Integer&gt;和&lt;#2扩展数字&gt;。 #2可能是Double,所以你得到的最好线索是#3扩展了Number。 在第二种情况下,您需要替换&lt;#1 super Integer&gt;和&lt;#2 super Number&gt;。现在这意味着#2可以是Number,Object,Serializable中的任何一个; #1添加到该列表Comparable和Integer。组合可以是Number,Object(和T应该是Object);或者Serializable,Integer(和T可以是Serializable),所以最好的线索是T是未知类型扩展Object的List。

当然它只能到达Number,但你不能为同一个类型变量获得两个边界,所以必须让它在那个