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

时间:2012-07-19 22:25:00

标签: java generics

根据this entry in the 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 个答案:

答案 0 :(得分:4)

那么,根据规范,调用都不合法。但是为什么第一种类型检查而第二种不检查呢?

区别在于如何检查方法的适用性(特别参见§15.12.2§15.12.2.2)。

  • 对于适用的简单非通用g,参数Class<ArrayList>必须是Class<? extends Iterable<?>>的子类型。这意味着? extends Iterable<?>需要contain 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>的子类型。由于子类型是reflexisve,这是真的。

    同样,f适用,类型参数必须为within its bounds。这不是因为,正如我们上面所示,ArrayList不是Iterable<?>的子类型。

那为什么还要编译?

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

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

答案 1 :(得分:3)

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

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

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

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

答案 2 :(得分: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);
    }
}

答案 3 :(得分:0)

这些不完全相同:

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

不同之处在于g接受了“实现未知Iterable的未知类”,但ArrayList<T>约束实现Iterable<T>,而不是Iterable<?>,因此它不会不匹配。

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