带参数的通用方法与带通配符的非泛型方法

Jos*_*sen 18 java generics

根据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) {}
Run Code Online (Sandbox Code Playgroud)

直观地说,似乎这些定义应该是等价的.但是,调用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>)
Run Code Online (Sandbox Code Playgroud)

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

class Test {
    <T extends Iterable<?>> void f(Class<T> x) {
        g(x);
    }
    void g(Class<? extends Iterable<?>> x) {
        f(x);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

<T::Ljava/lang/Iterable<*>;>(Ljava/lang/Class<TT;>;)V;
Run Code Online (Sandbox Code Playgroud)

g()具有通用签名

(Ljava/lang/Class<+Ljava/lang/Iterable<*>;>;)V;
Run Code Online (Sandbox Code Playgroud)

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

Ben*_*ulz 4

嗯,根据规范,这两种调用都是不合法的。但为什么第一个会进行类型检查,而第二个则不会?

\n\n

不同之处在于如何检查方法的适用性(特别参见\xc2\xa715.12.2\xc2\xa715.12.2.2 )。

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

    \n\n

    通过\xc2\xa74.10.2任何参数化C<...>都是原始类型的(直接)子类型CArrayList<?>的子类型也是如此ArrayList,但反之则不然。顺便说一下,ArrayList不是 的子类型Iterable<?>

    \n\n

    因而g不适用。

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

    \n\n

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

  • \n
\n\n

那么为什么它还是能编译呢?

\n\n

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

\n\n

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

\n