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

2022-09-02 22:32:48

根据 Java 泛型常见问题解答中的此条目,在某些情况下,泛型方法没有使用通配符类型的等效非泛型方法。根据这个答案,

如果方法签名使用多级通配符类型,则泛型方法签名与其通配符版本之间始终存在差异。

他们给出了一个方法的例子,该方法“需要一个相同类型的框列表”。通配符版本 “接受不同类型的框的异构列表”,因此不等效。<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 Testf()

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

并具有通用签名g()

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

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


答案 1

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

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

  • 要使简单的非泛型适用,该参数必须是 的子类型。这意味着需要包含,写成。规则 41 可以传递方式应用,因此需要是 的子类型。gClass<ArrayList>Class<? extends Iterable<?>>? extends Iterable<?>ArrayListArrayList <= ? extends Iterable<?>ArrayListIterable<?>

    根据§4.10.2,任何参数化都是原始类型的(直接)子类型。所以 是 的子类型,但不是相反。可传递地,不是 的子类型。C<...>CArrayList<?>ArrayListArrayListIterable<?>

    因此不适用。g

  • f是泛型的,为简单起见,让我们假设类型参数是显式指定的。要测试适用性,需要是 的子类型。由于子类型是反射性的,所以这是真的。ArrayListfClass<ArrayList>Class<T> [T=ArrayList] = Class<ArrayList>

    同样,为了适用,类型参数需要在其范围内。正如我们上面所显示的,这并不是因为 不是 的子类型。fArrayListIterable<?>

那么为什么它还要编译呢?

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

我不知道为什么javac的行为是一样的,但我假设出于类似的原因。您会注意到,javac 在将 raw 分配给任何一个时都不会发出未经检查的警告。ArrayListIterable<?>


答案 2

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

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

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

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


推荐