具有参数的通用方法 vs. 使用通配符的非通用方法

18
根据Java泛型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;

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


+1 对于一个泛型问题,如果回答不是“你不能使用泛型做到这一点”,那就好了。 - millimoose
语义上的差异是可以理解的,但我觉得它不能编译很奇怪。不过,从 gIterable 中移除通配符确实可以编译通过:void g(Class<? extends Iterable> x) - cklab
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<...> 都是(直接)原始类型 C 的子类型。因此,ArrayList<?>ArrayList 的子类型,但反之不成立。由此推出,ArrayList 不是 Iterable<?> 的子类型。

    因此,g 不适用。

  • f 是泛型的,为简单起见,假设类型参数 ArrayList 已明确指定。要测试 f 是否适用,需要使 Class<ArrayList>Class<T> [T=ArrayList] = Class<ArrayList> 的子类型。由于子类型是 自反的,因此为真。

    此外,要使 f 适用,类型参数需要 在其界限内。但由于我们已经证明,ArrayList 不是 Iterable<?> 的子类型,所以它不符合条件。

那么为什么它还是编译成功了呢?

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

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


3
如果类型参数是通配符参数化类型,则不会出现问题:
Class<ArrayList<?>> foo = null;
f(foo);
g(foo);

我认为这几乎肯定是一个奇怪的情况,起因是类文字的类型为Class<ArrayList>,因此在这种情况下,类型参数(ArrayList)是原始类型,原始ArrayList和通配符参数化ArrayList<?>之间的子类型关系非常复杂。
我没有仔细阅读语言规范,所以我不确定为什么显式类型参数的子类型适用而通配符类型不适用。它也很有可能是一个错误。

+1 对于泛型类的类字面量为什么要使用原始类型而不是通配符类型,这一点我一直不太清楚。 - Paul Bellora
@PaulBellora:有趣的是,这个问题在Scala中不会出现,它要求我对泛型类的类字面量进行参数化。我可以成功地使用 classOf [java.util.ArrayList[_]] 从Scala调用两种方法。 - Josh Rosen
@newacct:这可能是一个bug。虽然Class<? extends Iterable<?>>无法从Class<ArrayList>分配,但我可以编写一个方法<T extends Iterable<?>> Class<? extends Iterable<?>> convert(Class<T> x) { x },将ArrayList.class转换为Class<? extends Iterable<?>>。直接赋值Class<? extends Iterable<?>> cls = ArrayList.class;失败了,但是Class<? extends Iterable<?>> cls = convert(ArrayList.class);却可以工作,即使convert()没有执行任何强制类型转换。这使我能够调用g(convert(ArrayList.class)) - Josh Rosen

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);
    }
}

在您的第二个示例中,为什么对 f(d) 的调用成功而对 g(d) 的调用无法编译?类比于问题中描述的 Box 类,签名 void g(ArrayList<? extends Iterable<?>> x) 是否表示 g() 接受一个可能是不同类型的可迭代对象(或可迭代对象子类)的 ArrayList,而 <T extends Iterable<?>> void f(ArrayList<T> x) 则接受一个所有可迭代对象都是相同类型的 ArrayList?这种推理方式如何扩展到 Class<? extends Iterable<?>>,其中 Class 不是集合? - Josh Rosen
我不知道,我会假设出于与您的代码无法编译的相同原因。 我使用了两次ArrayList,以确保它不是特定于Class类的东西。 如果您愿意,可以将其更改为任何参数化类型,例如box。 我的观点是,它看起来像参数化类型在嵌套通配符上被清除,类似于@newacct所提到的内容。 - dfb

0

这两者并不完全相同:

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

区别在于 g 接受一个 "实现未知 Iterable 的未知类" 的 Class,但 ArrayList<T> 实现的是 Iterable<T>,而不是 Iterable<?>,因此不匹配。

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


你这里说的没有意义。ArrayList<T> 实现了 Iterable<T> - Paul Bellora
@PaulBellora 没错。ArrayList<T> 并没有 实现 Iterable<?>。请记住,就泛型而言,Iterable<T> 不是 Iterable<?> 的实例。 - Bohemian
你还是没有讲清楚。ArrayList 实现 Iterable<List> 的说法从哪里来的?其次,Iterable<?> 确实可以分配给任何 ArrayList<T> - Paul Bellora
@PaulBellora 嗯,是的。我确实指的是Iterable<T>(编辑了我的答案)。但仍然可以说ArrayList<T>不匹配,因为就匹配方法签名而言,Iterable<T>并不等同于Iterable<?> - T与类绑定,但ITerable<?>则不是 - 请参见我答案的最后一句话。 - Bohemian

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