views:

133

answers:

8

I am still experimenting with how Java handles generics. I stumbled upon the fact/issue/thing that if you have a generic interface like A<T>, you cannot really check afterwards if some object is actually implementing A<B> or A<C>.

I wondered if that could cause actual problems.

Now I have tried this code:

static interface A<T> { void foo(T obj); }
static class B implements A<B> {
    public void foo(B obj) { obj.bar(); }       
    void bar() {}
}
static {
    assert (new B() instanceof A<?>);
    ((A<?>) new B()).foo(new Object());
}

This gives me this error (for the foo-call):

The method foo(capture#1-of ?) in the type Main.A<capture#1-of ?> is not applicable for the arguments (Object)

I wonder why that is. Eclipse tells me that the signature of foo after the cast to A<?> is foo(? obj) which I thought is the same as foo(Object obj).

The assert succeeds.

What I tried to figure out is at what point exactly does it cast the object when I call the foo function.

Also, how can I call foo from A<?>? This is the thing I actually need to be able to do. Or is that impossible with any other parameter than null?

A more real-world example where I actually wonder about this: I use the Comparable<T> interface a lot. That case is actually even more complicated; I might open another question about that if this here doesn't answer it.

A: 

As you can probably imagine the T is erased on compile time and replaced with Object so you end up getting

static interface A { void foo(Object obj); }

In which your implementation would end up looking like: (the implementation may vary)

static class B implements A {
    public void foo(Object obj) { 
         ((B)obj).bar(); 
     }       
    void bar() {}
}

The casting happens after the erasure. You shouldn't have to worry about casting generic types, seldom does it make sense, and if it does it really should be documented.

John V.
A: 

The compiler is correct because it does a compile-cast test on compile-time.

((A<?>) new B()).foo(new Object());

is errornous because the compiler expected

((A<?>) new B()).foo(A object)....

meaning that it wanted anything of type A or its children. Object is the parent of A and it doesn't match the compile-test Parameter type for compilation.

The Elite Gentleman
+10  A: 

I wonder why that is. Eclipse tells me that the signature of foo after the cast to A is foo(? obj) which I thought is the same as foo(Object obj).

Nope, absolutely not. Imagine A<T> is List<T> with foo(T) being add(T) and so A<?> is List<?>. Should you be able to do this?

 List<String> strList = new ArrayList<String>();
 List<?> wcList = strList;
 wcList.add(Integer.valueOf(6));  //possible if add(?) is same as add(Object)

 //...
 String str = strList.get(0);

Of course not, since you'd get a ClassCastException in the final line.

What foo(?) really means is that the method applies to some unknown but specific type. You can't typically invoke these methods unless you pass null as the parameter, which is acceptable to assign to any reference type.

Mark Peters
Just to clarify (because this is the most important thing for me): There is no way to call `foo` from `A<?>`?
Albert
@Albert: not without passing null as the parameter value, no. You could always invoke it by using the raw type `((A)someA).foo(param)` but obviously that would be completely discarding any type safety you wanted to have.
Mark Peters
Ah, so instead of using `A<?>`, I can use just `A`? That was the actual thing I wanted to know here, thanks! :) But my other questions still stay open then (e.g. where is the cast done).
Albert
A: 

When you check if an instance of B is an instance of A<?> you just do the same thing as new B() instanceof A. You don't really check how T is set, it's just "something".

Later in the code with the cast, you tell that you'll use B as a A<?> so the variable will have the characteristic of a A but the generic type is still "something". This "something" exists and is probably a specified class but your don't care of the exact type.

That's why when you use the foo() method which take a T in parameter, you can't pass a "something" in parameter, because you don't know what it is, it could be an Object but it could be anything else.

Because of this the compiler tells you foo(capture#1-of ?) isn't applicable for the argument Object. The needed parameter is "something" but not necessarily an Object.


Then, when would you need this feature ?

For example if you work with a map, if you don't really care of the type of the key (if you only work with the values() method for example), you could do something like this :

Map<?, V> m 

This way you won't be able to use features related to the key of the map (but you don't care about that), but you'll be able to use a map with any kind of key.

Colin Hebert
+3  A: 

If you have "foo(? obj)" then the ? could be any type. If it is say String then you can't pass, say, an Integer to it. All you can pass is null.

Casting and use of instanceof should normally be avoided unless unavoidable (such as implementing equals), particularly with generics.

Tom Hawtin - tackline
Just to clarify: So there is no way at all that I can call `foo` from `A<?>` with any other parameter than `null`?
Albert
A: 

No, foo(? obj) is not actually the same as foo(Object obj). The difference is, when the parameter type is Object, it's explicitly stating that any type of object is legal. With a parameter type of ?, the method states that it does not know what type of object is legal... therefore nothing is legal except for null.

The reasoning for this becomes apparent when you consider a List rather than an arbitrary interface. Look at this method:

public void foo(List<?> list) {
  list.add(...); // what can we add here?
}

The ? indicates that any type of List is acceptable... the List passed in could be a List<String>, a List<Integer> or a List<Map<Foo, Bar>>. There's no way of knowing. Note that this is only a problem with methods that consume generic parameters, such as add or your foo method. For methods that produce (return) an object of the generic type, it's fine to use such a method and assign the result as an Object. This, unlike the consumer method, cannot corrupt the internal state of the generic object.

Also notice that when you call ((A<?>) new B()).foo(new Object()), you're trying to do something illegal... if something (such as the Object) that is not a B were to be passed to B's foo method, it would explode at runtime. The compiler is correctly preventing you from doing this.

You may also want to check out my answer to another question here which explains some things about bounded wildcard types and such.

ColinD
A: 

This is a simple explanation:

A<?> means A of unknown parametrized type. Given

A<?> ref = new B()

ref could point to any A: A<Object, A<String>, anything. So you can not call A.foo(new Object()) because in case of A<?> reference there is no way to know which parameter ref.foo() accepts. Actually the only thing valid is ref.foo(null).

Peter Knego
A: 

Many have clarified that point about ? but noone has really answered the main question yet, so here it is:

It is possible to call A#foo like this:

((A) new B()).foo(new Object());

The cast then is done inside of foo.

Albert
This turns A into a `raw type` (http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#110257) which is generally considered fantastically bad programming practice.
Steven Schlansker
May I know who did the downvote? Of course this is bad practice and the compiler also warns about this but this is still the exact answer to the question. It is exactly the thing which I wanted to know here. I am not actually using this. But a downvote looks like it is a wrong answer, which it is not (at least from what I have understand). If you think this answer is wrong, please explain why and don't just downvote without any comment...
Albert