Bounded wildcards and type compatibility

  • Thread starter John C. Bollinger
  • Start date
J

John C. Bollinger

Neither version 1.5.0_06 of Sun's compiler nor version 3.1.1 of
Eclipse's will compile the following code, but I can't figure out what's
wrong with it:

====

package test;

public class TestTypeBounds {

public void foo(Bar<? extends Foo, Baz<? extends Foo>> bar) {
}

public <T extends Foo> void doFoo(Bar<T, Baz<? extends T>> bar) {
foo(bar); // <=== incompatible types here (?)
}
}

class Foo {}

class Bar <X, Y> {}

class Baz <Z> {}

====

Both compilers complain about incompatible types in the invocation of
foo(). Eclipse's compiler seems to have fewer bugs related to
parameterized types than does Sun's (0 vs. 2 on my personal scorecard,
before this), but when both compilers reject the code it's a pretty good
sign that the code is wrong. For the life of me, however, I don't see
the problem. Does anyone else see anything wrong?
 
J

John C. Bollinger

I said:
Neither version 1.5.0_06 of Sun's compiler nor version 3.1.1 of
Eclipse's will compile the following code, but I can't figure out what's
wrong with it:

[...]

I finally had the flash of insight just after I posted. It figures.
I'm now guessing that the situation is just a more complicated version
of Foo<Object> not being a supertype of Foo<String>. Simplifying it a
bit, even though their type parameters match up fine individually,
Foo<?, ?> is not a supertype of Foo<T, T> because the relationship
between the type parameters in the latter type is not represented by the
former type.

Does that make sense? I'm not quite sure it does -- I'm still not
seeing why Foo<?, ?> shouldn't be a valid generalization of Foo<T, T>.
 
O

opalpa

I'm gonna try to talk out the code, that is convert the statements into
English.

Some of the easy stuff is that:

Bar is a class paramterized on two things.
Baz is a class paramterized on one thing.
Foo is not paramterized.

There are two methods left and that is the hairy stuff.
Let's look at the one invoked first:

doFoo has an argument which is a Bar and we know that Bar is
paramterized on two things. doFoo's Bar argument is paramterized on a
subclass of Foo and a Baz which is itself paramterized on a subclass of
a subclass of Foo (right?). That does not make sense to me, T extends
Foo and Baz is paramterize on ? extends T. Does that mean that we need
a subclass of a subclass?

Could we write doFoo like so:

public <T extends Foo> void doFoo(Bar<T, Baz<T>> bar) {

Compiler does not accept it. Why not?

It accepts:
public <T extends Foo> void doFoo(Bar<? extends Foo, Baz<? extends
Foo>> bar) {

But I cannot substitute "? extends Foo" with T. Should that be the
case?


The generics stuff clearly opens up possibilites that are hard to think
about. It's like C++ stuff. Fortunately in practice code (my code
anyway) never gets to this point. Generics in practice seem to be
conveniences for reducing typing casts.
 
J

John C. Bollinger

Hello, should line

public <T extends Foo> void doFoo(Bar<T, Baz<? extends T>> bar) {

be

public <T extends Foo> void doFoo(Bar<T, Baz<? extends Foo>> bar) {

?

Thanks, but no, it shouldn't. Your proposed change means something
different from the original code. It does not in any case solve the
problem, for I find that the type bounds aren't the issue after all.
For example, this version also doesn't compile:

====

package test;

public class TestWildcards {

public void foo(Bar<Baz<?>> bar) {
}

public <T> void doFoo(Bar<Baz<T>> bar) {
foo(bar); // <=== Incompatible types here (still)
}
}

class Bar <X> {}

class Baz <Z> {}

====

This version clarifies the problem, I think, to the extent that I can
now articulate it clearly. It is true that Baz<T> is a subtype of
Baz<?>, but that does not make Bar<Baz<T>> a subtype of Bar<Baz<?>>, the
wildcard notwithstanding. This is in fact correct, as it is exactly
analogous to List<Object> vs. List<String>. Adding additional type
parameters to Bar makes it harder to identify the real issue, but
doesn't fundamentally change it.
 
T

Torkel Franzen

John C. Bollinger said:
It is true that Baz<T> is a subtype of
Baz<?>, but that does not make Bar<Baz<T>> a subtype of Bar<Baz<?>>, the
wildcard notwithstanding.

Yep. See the rules about containment in 4.5.1.1 and about subtyping
in 4.10.2.
 
J

John C. Bollinger

I'm gonna try to talk out the code, that is convert the statements into
English.

Some of the easy stuff is that:

Bar is a class paramterized on two things.
Baz is a class paramterized on one thing.
Foo is not paramterized.

Good up to here.
There are two methods left and that is the hairy stuff.
Let's look at the one invoked first:

doFoo has an argument which is a Bar and we know that Bar is
paramterized on two things. doFoo's Bar argument is paramterized on a
subclass of Foo and a Baz which is itself paramterized on a subclass of
a subclass of Foo (right?). That does not make sense to me, T extends
Foo and Baz is paramterize on ? extends T. Does that mean that we need
a subclass of a subclass?

No, you're missing an important point here. doFoo's argument is
characterized a particular subclass of Foo, and on a Baz which is itself
characterized by a subclass of *the same* subclass of Foo. The bound on
the method's type parameter doesn't really matter here, it turns out;
you could remove it altogether without fundamentally changing what I
intend to express.
Could we write doFoo like so:

public <T extends Foo> void doFoo(Bar<T, Baz<T>> bar) {

That means something different, but I could work with it if the compiler
would accept it. As you observed (elided), it does not. I have now
figured out why (see my most recent previous post), so now I just have
to determine how to handle the situation.

For what it's worth, this whole issue arose when trying to mangle
perfectly good generic code (that Eclipse handles fine) to work around
bugs in Sun's javac. Why is it that Eclipse can handle Java generics
better than Sun itself can?

[...]
The generics stuff clearly opens up possibilites that are hard to think
about. It's like C++ stuff. Fortunately in practice code (my code
anyway) never gets to this point. Generics in practice seem to be
conveniences for reducing typing casts.

I find it most useful to think about generics in terms of describing
types. That seems trivial to say, but you are bound to run into trouble
when you lose sight of it. Reducing casts, though touted as a major
benefit of generics, is just a side effect of the deep change in the
type system that generics bring. Parameterized types are a whole
conceptual level higher than unparameterized ones, and thus they do
present considerably greater challenges.
 

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

Similar Threads


Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,175
Latest member
Vinay Kumar_ Nevatia
Top