具有通配符的泛型方法与使用通配符的非泛型方法 [英] Generic method with parameters vs. non-generic method with wildcards

查看:99
本文介绍了具有通配符的泛型方法与使用通配符的非泛型方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



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


如果方法签名使用多级通配符类型,那么通用方法签名和

他们给出了一个方法示例< T> void print1(列表< Box< T>>列表),其中需要相同类型的列表。通配符版本 void print2(List< Box> list>< / code>)接受不同类型框的异构列表,因此不是等同的。



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

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

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


$ b

  g(java.lang。 Test 
中的Class< ;? extends java.lang.Iterable<>>)不能应用于(java.lang.Class< java.util.ArrayList>)


$ b pre class =lang-java prettyprint-override> class Test {
< T extends Iterable<>> void f(Class< T> x){
g(x);
}
void g(Class <?extends Iterable <?>> x){
f(x);




$ b

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

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

g()具有通用签名

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

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

解决方案

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



不同之处在于如何检查方法的适用性(请参阅第15.12.2节§15.12.2.2)。




  • 对于简单的非泛型 g Class< ArrayList> 需要是 Class的子类<?扩展Iterable<>> 。这意味着?扩展Iterable<?> 需要包含 ArrayList ,写成 ArrayList <=?扩展Iterable<?> 。规则 4 1 可以被传递,因此 ArrayList 需要是 Iterable <?>< / code的子类型>。



    按照§4.10.2任何参数化 C< ...> 是一个(直接)子类型原始类型 C 。所以 ArrayList<?> ArrayList 的子类型,但不是相反。过渡性地, ArrayList 不是 Iterable <?>< / code>的子类型。



    因此 g 是不适用的。

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



    也可以使用 f ,type参数需要在其范围内。这不是因为,正如我们上面所示, ArrayList 不是 Iterable <?>< / code>的子类型。



那为什么要编译?



这是一个错误。遵循错误报告后续修复 JDT编译器明确排除了第一种情况(类型参数包含)。由于JDT认为 ArrayList Iterable <?>> 的子类型,所以第二种情况仍然被高兴地忽略。 TypeBinding.isCompatibleWith(TypeBinding))。



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


According to this entry in the Java Generics FAQ, there are some circumstances where a generic method has no equivalent non-generic method that uses wildcard types. According to that answer,

If a method signature uses multi-level wildcard types then there is always a difference between the generic method signature and the wildcard version of it.

They give the example of a method <T> void print1( List <Box<T>> list), which "requires a list of boxes of the same type." The wildcard version, void print2( List <Box<?>> list), "accepts a heterogenous list of boxes of different types," and thus is not equivalent.

How do you interpret the the differences between the following two method signatures:

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

Intuitively, it seems like these definitions should be equivalent. However, the call f(ArrayList.class) compiles using the first method, but the call g(ArrayList.class) using the second method results in a compile-time error:

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

Interestingly, both functions can be called with each others' arguments, because the following compiles:

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

Using javap -verbose Test, I can see that f() has the generic signature

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

and g() has the generic signature

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

What explains this behavior? How should I interpret the differences between these methods' signatures?

解决方案

Well, going by the spec, neither invocation is legal. But why does the first one type check while the second does not?

The difference is in how the methods are checked for applicability (see §15.12.2 and §15.12.2.2 in particular).

  • For simple, non-generic g to be applicable, the argument Class<ArrayList> would need to be a subtype of Class<? extends Iterable<?>>. That means ? extends Iterable<?> needs to contain ArrayList, written ArrayList <= ? extends Iterable<?>. Rules 4 and 1 can be applied transitively, so that ArrayList needs to be a subtype of Iterable<?>.

    Going by §4.10.2 any parameterization C<...> is a (direct) subtype of the raw type C. So ArrayList<?> is a subtype of ArrayList, but not the other way around. Transitively, ArrayList is not a subtype of Iterable<?>.

    Thus g is not applicable.

  • f is generic, for simplicity let us assume the type argument ArrayList is explicitly specified. To test f for applicability, Class<ArrayList> needs to be a subtype of Class<T> [T=ArrayList] = Class<ArrayList>. Since subtyping is reflexisve, that is true.

    Also for f to be applicable, the type argument needs to be within its bounds. It is not because, as we've shown above, ArrayList is not a subtype of Iterable<?>.

So why does it compile anyways?

It's a bug. Following a bug report and subsequent fix the JDT compiler explicitly rules out the first case (type argument containment). The second case is still happily ignored, because the JDT considers ArrayList to be a subtype of Iterable<?> (TypeBinding.isCompatibleWith(TypeBinding)).

I don't know why javac behaves the same, but I assume for similar reasons. You will notice that javac does not issue an unchecked warning when assigning a raw ArrayList to an Iterable<?> either.

这篇关于具有通配符的泛型方法与使用通配符的非泛型方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆