Generic type cast involving wildcard type

D

Daniel Thoma

Hi!

I can't figure out, why casting to a subclass causes an unchecked cast
warning, when a wildcard type is involved, but seems to work in any other
case:

Example:
static class Base<T> { }

static class Derived<T> extends Base<T> { }

<T> void foo() {
Base<? extends CharSequence> base = new Derived<String>();
Derived<? extends CharSequence> derived
= (Derived<? extends CharSequence>) base; // causes warning

Base<T> base2 = new Derived<T>();
Derived<T> derived2 = (Derived<T>) base2;

Base<String> base3 = new Derived<String>();
Derived<String> derived3 = (Derived<String>) base3;
}

Compiler warning:
warning: [unchecked] unchecked cast
found : test.Main.Base<capture#325 of ? extends java.lang.CharSequence>
required: test.Main.Derived<? extends java.lang.CharSequence>
Derived<? extends CharSequence> derived = (Derived<? extends CharSequence>)
base;

Is there an explanation for this behavior?

Daniel
 
L

Lew

Hi!

I can't figure out, why casting to a subclass causes an unchecked cast
warning, when a wildcard type is involved, but seems to work in any other
case:

Example:
static class Base<T> { }

static class Derived<T> extends Base<T> { }

<T> void foo() {
  Base<? extends CharSequence> base = new Derived<String>();
  Derived<? extends CharSequence> derived
        = (Derived<? extends CharSequence>) base; // causes warning

  Base<T> base2 = new Derived<T>();
  Derived<T> derived2 = (Derived<T>) base2;

  Base<String> base3 = new Derived<String>();
  Derived<String> derived3 = (Derived<String>) base3;

}

Compiler warning:
warning: [unchecked] unchecked cast
found   : test.Main.Base<capture#325 of ? extends java.lang.CharSequence>
required: test.Main.Derived<? extends java.lang.CharSequence>
        Derived<? extends CharSequence> derived = (Derived<? extends CharSequence>)
        base;

Is there an explanation for this behavior?

Casting is a runtime operation. By run time, all generics information
has been erased, so an attempt to do a generics-aware cast will yield
an "unchecked" warning. This is true for all generic casts except
those involving an unbound wildcard.

Part of the rationale is that such casts cannot be proven safe at
compile time.

A workaround is to use '@SuppressWarnings( "unchecked" )' at the
declaration of
'Derived<? extends CharSequence> derived'.

This annotation must be used most carefully only in places where you
can demonstrate conclusively that it's safe to do so, and you should
document that demonstration in a comment at the point of annotation.
If you make a mistake, you will get a 'ClassCastException' at runtime,
just the sort of problem generics were designed to prevent.

A better solution would be to complete your type analysis so that the
downcast is not needed.

For details and background, see:
<http://java.sun.com/docs/books/tutorial/extra/generics/index.html>
<http://java.sun.com/docs/books/effective/generics.pdf>
 
M

markspace

Daniel said:
Is there an explanation for this behavior?


I think it's because generics aren't reified. In other words, there's
no way for the compiler to test that "base" actually does parameterize
<? extends CharSequence>. All it can do is test that "base" is of type
"Derived" (which basically means Derived<?>) and that's it. You only
have raw types at runtime.

Thus the compiler is just telling you that you're getting a pig in a
poke, and asks that you acknowledge that fact with a little
@SuppressWarnings.
 
D

Daniel Thoma

Hi Lew,

thank's for your answer.
Casting is a runtime operation. By run time, all generics information
has been erased
Yes, I'm aware of that.
This is true for all generic casts except those involving an unbound
wildcard.
That's know the whole truth, a generic cast causes a warning, only if
generic type information would be neccessary to check the cast at runtime.

These two examples work fine, because the cast only affects the class, not
the type parameters:The type information isn't available at runtime, but the compiler can infer,
that the type parameters will always have the required type.

So my question is, why isn't that possible, when using wildcard types:
Base<? extends CharSequence> base = new Derived<String>();
Derived<? extends CharSequence> derived
= (Derived<? extends CharSequence>)base;
The compiler should be able to infer, that the type-variable has always the
lower bound CharSequence.

Daniel
 
M

markspace

Daniel said:
The compiler should be able to infer, that the type-variable has always the
lower bound CharSequence.


I don't think it can. That's the issue. When you do this:

derived = (Derived<...>) base;

The only part of that that the runtime can actually check is the
(Derived) part. In other words, because a cast is an actual runtime
operation, and the cast can only check part of what it is being asked to
check, it issues a warning.

That's different from something like:

Derived<?> derived = new Base<String>();

Which needs no runtime checks, it's all done at compile time.
 
D

Daniel Thoma

You mean something like this:
Derived<?> derived = (Derived<?>) new Base<String>();

But why is it then, that something like this works:
Derived<String> derived = (Derived<String>) new Base<String>();

The compiler can obviously sometimes infer, that a generic cast can be
savely "simplified" to a normal cast.
If that doesn't work with wildcard types, there has to be some reason for
that. It's either a compiler bug, a specified limitation of the language or
there has to be an example, where such a cast would be unsafe, i.e. the cast
could cause a ClassCastException at some later point in the program, like in
this, obvious case:

List<?> list = new List<Object>();
List<String> list2 = (List<String>) list1; //unsafe warning
String s = list2.get(0); //May cause ClassCastException

Lets consider this example:
List<? extends CharSequence> list = ...
ArrayList<? extends CharSequence> list2
= (ArrayList<? extends CharSequence>) list
CharSequence s = list2.get(0);

Is there something, I could insert for "..." in the first line, such that I
get a ClassCastException in the last line.
 
A

Alessio Stalla

Hi!

I can't figure out, why casting to a subclass causes an unchecked cast
warning, when a wildcard type is involved, but seems to work in any other
case:

Example:
static class Base<T> { }

static class Derived<T> extends Base<T> { }

<T> void foo() {
  Base<? extends CharSequence> base = new Derived<String>();
  Derived<? extends CharSequence> derived
        = (Derived<? extends CharSequence>) base; // causes warning

  Base<T> base2 = new Derived<T>();
  Derived<T> derived2 = (Derived<T>) base2;

  Base<String> base3 = new Derived<String>();
  Derived<String> derived3 = (Derived<String>) base3;

}

Compiler warning:
warning: [unchecked] unchecked cast
found   : test.Main.Base<capture#325 of ? extends java.lang.CharSequence>
required: test.Main.Derived<? extends java.lang.CharSequence>
        Derived<? extends CharSequence> derived = (Derived<? extends CharSequence>)
        base;

Is there an explanation for this behavior?

I believe the explanation lies in the correct interpretation of the ?
wildcard; I think I got struck by that too.

? extends X does not mean "any class that extends X", but "a given
class that extends X, which one I don't know".

So when you use <T> or <String> in your cast, the compiler knows
you're referring to the very same class; when you use <? extends
CharSequence>, the compiler understands that you're referring to two
classes that extend CharSequence, which might or might not be the same
one.

You cannot cast from List<T extends X> to List<S extends X>, and
using ? is just like using T or S, except that you don't name the
type.
 
T

Tom Anderson

I can't figure out, why casting to a subclass causes an unchecked cast
warning, when a wildcard type is involved, but seems to work in any other
case:

Example:
static class Base<T> { }

static class Derived<T> extends Base<T> { }

<T> void foo() {
Base<? extends CharSequence> base = new Derived<String>();
Derived<? extends CharSequence> derived
= (Derived<? extends CharSequence>) base; // causes warning

Base<T> base2 = new Derived<T>();
Derived<T> derived2 = (Derived<T>) base2;

Base<String> base3 = new Derived<String>();
Derived<String> derived3 = (Derived<String>) base3;
}

Compiler warning:
warning: [unchecked] unchecked cast
found : test.Main.Base<capture#325 of ? extends java.lang.CharSequence>
required: test.Main.Derived<? extends java.lang.CharSequence>
Derived<? extends CharSequence> derived = (Derived<? extends CharSequence>)
base;

Is there an explanation for this behavior?

Yes - you can't cast from one question mark to the other, because they're
different question marks.

Seriously.

There's no explicit link between the "? extends CharSequence" on base and
the "? extends CharSequence" on derived. Thus, they're different
'captures' of the "? extends CharSequence" wildcard (as subtly hinted in
the error message). As far as the compiler is concerned, those ?s could
refer to quite different types (remember a ? means 'some specific type,
but one which isn't fixed at compile time'), and so that cast isn't
guaranteed to work (and can't be checked at runtime, because java's
generics aren't reified).

You can fix this by adding a type parameter to the method - declare <Q
extends CharSequence> and replace "? extends CharSequence" with Q.

Although in your example, this won't work, because you then try to assign
a Derived<String> to it, which is obviously no good, because there is no
static guarantee that Q includes String. If you change base to being a
method parameter rather than a local, thus magically ignoring the question
of its actual value, it compiles cleanly.

This is quite arcane stuff, which is why all the answers you've had up to
Alessio's have got it completely wrong!

tom
 
D

Daniel Thoma

First of all, I found an interesting workaround:

Base<? extends CharSequence> base4 = new Derived<String>();
Derived<? extends CharSequence> derived4 = cast(base4);

<U> Derived<U> cast(Base<U> base) {
return (Derived<U>) base;
}

This example causes no unchecked warning. I now tried to read the JLS in
more detail and came up with the following explanation:

In the second line on the evaluation of base4 capure conversion is applied
(JLS 5.1.10) which results in a type captureX extends CharSequence].
Now typinference yields U = captureX. Therefore case(base4) has has a return
type of Derived<captureX>. Derived<captureX> is a subtype of Derived<?
extends CharSequence> (JLS 4.10.2), therefore the result can be assigned to
derived4.

As I understand the JLS, capture conversion is never carried out for the
type of derived4.
The direct cast should behave any different. Capture-conversion will be
applied to the cast-expression, resulting in a type Derived<captureX>. The
compiler message supports this, since it only mentions a capture variable
for the "found"-part.
So the question to me is, why is Derived<captureX> assingable to Derived<?
extends CharSequence> in case of the method invokation but not in case of
the direct cast.
 
L

Lew

Daniel said:
First of all, I found an interesting workaround:

Base<? extends CharSequence> base4 = new Derived<String>();
Derived<? extends CharSequence> derived4 = cast(base4);

<U> Derived<U> cast(Base<U> base) {
return (Derived<U>) base;
}

This example causes no unchecked warning. I now tried to read the JLS in
more detail and came up with the following explanation:

In the second line on the evaluation of base4 capure conversion is applied
(JLS 5.1.10) which results in a type captureX extends CharSequence].
Now typinference yields U = captureX. Therefore case(base4) has has a return
type of Derived<captureX>. Derived<captureX> is a subtype of Derived<?
extends CharSequence> (JLS 4.10.2), therefore the result can be assigned to
derived4.

As I understand the JLS, capture conversion is never carried out for the
type of derived4.
The direct cast should behave any different. Capture-conversion will be
applied to the cast-expression, resulting in a type Derived<captureX>. The
compiler message supports this, since it only mentions a capture variable
for the "found"-part.
So the question to me is, why is Derived<captureX> assingable to Derived<?
extends CharSequence> in case of the method invokation [sic] but not in case of
the direct cast.

Because the non-wildcard type parameter U captures the exact capture of the
'base' parameter and pins it to the method return's type parameter, and then
when you assign the return value to the wildcarded variable that variable in
turn is able to pick up on this same capture, but in the process effectively
"forgetting" which capture it picked up. I think of the method as "stepping
down" the wildcard to a specific capture, then the assignment of the return
value to a variable as "stepping up" the capture to a wildcard. In between,
the method ensures a clean pass-through of the type.

Without the method the compiler doesn't have enough information to guarantee
the compatibility of the step-down and step-up. I've seen similar idioms in
the literature for Java generics, whereby a helper method nails down the type
parameter.
 
D

Daniel Thoma

What I know from literature is something like this:

List<?> list = new List<String>();

list.add(list.get(0)); // fails

foo(list); // works
void <T> foo(List<T> list) {
list.add(list.get(0));
}

And in this case, I can follow, because the compiler would have to transport
the information, that he is dealing with one list (with a fixed but unknown
type) in both parts of the expression. And it is not so easy to guarantee,
that "list" doesn't change its value in between.
In the casting scenario I can not realy follow that explanation. As I see
it, there aren't even two capture variables involved.
 
T

Tom Anderson

What I know from literature is something like this:

List<?> list = new List<String>();

list.add(list.get(0)); // fails

foo(list); // works
void <T> foo(List<T> list) {
list.add(list.get(0));
}

And in this case, I can follow, because the compiler would have to transport
the information, that he is dealing with one list (with a fixed but unknown
type) in both parts of the expression. And it is not so easy to guarantee,
that "list" doesn't change its value in between.

If it's a local variable, it's pretty easy, and the kind of thing
compilers do all the time. Less so if it's an instance variable, true.
In the casting scenario I can not realy follow that explanation. As I see
it, there aren't even two capture variables involved.

There aren't - there are three.

List<?> high = new ArrayList<String>();
// ^ one
ArrayList<?> low = (ArrayList<?>)high;
// ^ two ^ three

I think the compiler could work out that this is safe - it could look at
the relatioship between the type variable in ArrayList and the one in
List, see that they're defined to be the same (by "ArrayList<E> implements
List<E>"), and deduce that the variables on high and low are the same.

I don't know that this would be a good idea, though. If you have two
variables bound to the same type, why not make that explicit by
introducing a type variable?

List<Q> high = new ArrayList<String>();
ArrayList<Q> low = (ArrayList<Q>)high;

The problem with this is that the only way to define a type variable is to
add it to the method signature. What would be useful is if we could
define them locally. Like:

type <Q>;
List<Q> high = new ArrayList<String>();
ArrayList<Q> low = (ArrayList<Q>)high;

Or something. Or maybe scoped to blocks (weird syntax alert!):

<Q> {
List<Q> high = new ArrayList<String>();
ArrayList<Q> low = (ArrayList<Q>)high;
}

Typically, though, this is a non-issue, since the ?-typed thing you're
operating on is coming in as a method parameter or something anyway, and
you can set up a type variable there.

The example code you posted was obviously contrived. What was your real
code? Can we see how we might solve the real problem?

tom
 
M

markspace

Tom said:
There aren't - there are three.

List<?> high = new ArrayList<String>();
// ^ one
ArrayList<?> low = (ArrayList<?>)high;
// ^ two ^ three


Thanks for posting this. You gave a great explanation on how wildcard
capture works. I was always a bit fuzzy on what is going on here. I
think I've managed to grok it better now.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top