18

根据Java Generics FAQ 中的这个条目,在某些情况下,泛型方法没有使用通配符类型的等效非泛型方法。根据那个答案,

如果方法签名使用多级通配符类型,则通用方法签名和通配符版本之间总是存在差异。

他们给出了一个方法的例子<T> void print1( List <Box<T>> list),它“需要一个相同类型的盒子列表”。通配符版本,void print2( List <Box<?>> list)“接受不同类型的盒子的异构列表”,因此不等效。

您如何解释以下两个方法签名之间的差异:

 <T extends Iterable<?>> void f(Class<T> x) {}
                         void g(Class<? extends Iterable<?>> x) {}

直觉上,这些定义似乎应该是等价的。但是,调用f(ArrayList.class)使用第一种方法进行编译,但g(ArrayList.class)使用第二种方法的调用会导致编译时错误:

g(java.lang.Class<? extends java.lang.Iterable<?>>) in Test
    cannot be applied to (java.lang.Class<java.util.ArrayList>)

有趣的是,这两个函数都可以用彼此的参数调用,因为以下编译:

class Test {
    <T extends Iterable<?>> void f(Class<T> x) {
        g(x);
    }
    void g(Class<? extends Iterable<?>> x) {
        f(x);
    }
}

使用javap -verbose Test,我可以看到它f()具有通用签名

<T::Ljava/lang/Iterable<*>;>(Ljava/lang/Class<TT;>;)V;

g()具有通用签名

(Ljava/lang/Class<+Ljava/lang/Iterable<*>;>;)V;

什么解释了这种行为?我应该如何解释这些方法的签名之间的差异?

4

4 回答 4

4

好吧,按照规范,这两种调用都不合法。但是为什么第一个类型检查而第二个没有呢?

不同之处在于如何检查方法的适用性(特别是参见§15.12.2§15.12.2.2)。

  • 要使简单的非泛型g适用,参数Class<ArrayList>需要是Class<? extends Iterable<?>>. 这意味着? extends Iterable<?>需要包含 ArrayList,书面ArrayList <= ? extends Iterable<?>。规则41可以传递应用,因此ArrayList需要是Iterable<?>.

    按照 §4.10.2,任何参数化C<...>都是 raw type 的(直接)子类型CArrayList<?>的子类型也是如此ArrayList,但反之则不然。传递地,ArrayList不是 的子类型Iterable<?>

    因此g不适用。

  • f是通用的,为简单起见,我们假设类型参数ArrayList是明确指定的。要测试f适用性,Class<ArrayList>需要是Class<T> [T=ArrayList] = Class<ArrayList>. 由于子类型是反身的,这是真的。

    同样为了f适用,类型参数需要在其范围内。这不是因为,正如我们上面所展示的,ArrayList它不是Iterable<?>.

那么为什么它仍然编译呢?

这是一个错误。在错误报告后续修复之后,JDT 编译器明确排除了第一种情况(类型参数包含)。第二种情况仍然被忽略,因为 JDT 认为ArrayListIterable<?>( TypeBinding.isCompatibleWith(TypeBinding)) 的子类型。

我不知道为什么 javac 的行为相同,但我假设出于类似的原因。ArrayList您会注意到 javac 在将 raw 分配给any 时不会发出未经检查的警告Iterable<?>

于 2012-07-20T21:42:18.377 回答
3

如果类型参数是通配符参数化类型,则不会出现问题:

Class<ArrayList<?>> foo = null;
f(foo);
g(foo);

我认为这几乎可以肯定是一个奇怪的情况,因为类文字的类型是Class<ArrayList>,所以这种情况下的类型参数 ( ) 是原始类型,原始和通配符参数化ArrayList之间的子类型关系很复杂.ArrayListArrayList<?>

我没有仔细阅读语言规范,所以我不确定为什么子类型在显式类型参数情况下有效,但在通配符情况下无效。它也很可能是一个错误。

于 2012-07-20T02:52:18.803 回答
0

猜:代表第一个的东西?(ArrayList) 不“实现” ArrayList<E>(通过双嵌套通配符)。我知道这听起来很有趣,但是......

考虑(对于原始列表):

 void g(Class<? extends Iterable<Object> x) {} // Fail
 void g(Class<? extends Iterable<?> x) {}  // Fail
 void g(Class<? extends Iterable x) {}  // OK

// Compiles
public class Test{
    <T extends Iterable<?>> void f(ArrayList<T> x) {}
    void g(ArrayList<? extends Iterable<?>> x) {}

    void d(){
        ArrayList<ArrayList<Integer>> d = new ArrayList<ArrayList<Integer>>();
        f(d);
        g(d);
    }
}

// Does not compile on g(d)
public class Test{
    <T extends Iterable<?>> void f(ArrayList<T> x) {}
    void g(ArrayList<? extends Iterable<?>> x) {}

    void d(){
        ArrayList<ArrayList> d = new ArrayList<ArrayList>();
        f(d);
        g(d);
    }
}
于 2012-07-19T23:07:55.690 回答
0

这些并不完全相同:

<T extends Iterable<?>> void f(Class<T> x) {}
void g(Class<? extends Iterable<?>> x) {}

不同之处在于g接受“实现未知的 Iterable 的未知类”,但ArrayList<T>受限于实现Iterable<T>, not Iterable<?>,因此不匹配。

为了更清楚,g会接受Foo implements Iterable<?>,但不会AraryList<T> implements Iterable<T>

于 2012-07-20T03:08:16.830 回答