Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic method with parameters vs. non-generic method with wildcards

Tags:

java

generics

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?

like image 273
Josh Rosen Avatar asked Jul 19 '12 22:07

Josh Rosen


People also ask

What is the difference between type parameters and wildcards?

The difference is that if you have a type parameter U, you can use that type inside the method; if you use a wildcard, you don't have access to the actual type inside the method (you only know that it is some unknown type that extends Number).

What is the difference between generic and wildcard in Java?

' as an actual type parameter. Wildcards are nothing but the question mark(?) that you use in the Java Generics. We can use the Java Wildcard as a local variable, parameter, field or as a return type. But, when the generic class is instantiated or when a generic method is called, we can't use wildcards.

What are wild cards in a generic method?

In generic code, the question mark (?), called the wildcard, represents an unknown type. The wildcard can be used in a variety of situations: as the type of a parameter, field, or local variable; sometimes as a return type (though it is better programming practice to be more specific).

Is generic code faster or slower than non generic code?

Not only can you do away with boxing but the generic implementations are somewhat faster than the non generic counterparts with reference types due to a change in the underlying implementation.


1 Answers

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.

like image 121
Ben Schulz Avatar answered Sep 28 '22 04:09

Ben Schulz