Interaction between covariance and generics

This is a follow-up of sorts to the "Implied types" thread but you don't have to read it to understand. Since responders on this forum often complain that they can't help because they don't understand the context; I give some context below, perhaps too much.

* There is a matching pair of Tag and Type classes at the top of the hierarchy and then a matching triplet of Foo, FooTag and FooType classes underneath.

* As in the earlier thread there is a 1-to-1 correspondence between Foo and FooTag classes. There is AFAICT no way to convey this to the compiler but it makes certain unchecked casts safe perfectly safe within the context of this system.

* Type classes are Class-like in character and thus parametrized over the Tag class of the same level. FooType is implemented as a kind of Enum and serves as a sort of Foo/FooTag ontology.

* FooTag and FooType extend Tag and Type respectively but FooType has an extra type parameter and thus extends Type only w/r to the first parameter; I suspect this is this root of my troubles.

* Tag classes have a getType method that is meant to be covariantly specialized as we move down the hierarchy.

* Finally, for convenience both Foo and FooTag have a getType method which, for matching Foo and FooTag instances, should return the exact same object. The obvious way to achieve this is through delegation but these are interfaces, so I implement this delegation in an AbstractFoo class and that is where things go awry.

Here is the code; I am only showing the top 3 levels: next comes a family of concrete classes implementing Foo by extending AbstractFoo, i.e., Foo1, Foo2 etc with matching FooTag1, FooTag1 etc.

// first level

interface Type<Textends Tag>{}

interface Tag{

Type<?extends Tag> getType();

}

// second level

class FooType<Textends FooTag, Fextends Foo><T>>implements Type<T>{}

interface FooTagextends Tag{

<Textends FooTag> FooType<T, Foo><T>> getType();

}

interface Foo<Textends FooTag>{

T getTag();

FooType<T, Foo><T>> getType();

}

// third level

abstractclass AbstractFoo<Textends FooTag>implements Foo<T>{

public FooType<T, Foo><T>> getType(){

return (FooType<T, Foo><T>>)getTag().getType();

}

}

Compilation fails with

reference to getType is ambiguous, both method getType() in Tag and method <T>getType() in FooTag match

return (FooType<T, Foo><T>>)getTag().getType();

^

Covariance seems to be working since the compiler does not complain about overriding Tag.getType with a different return type in FooTag.getType but then there is this error about the ambiguity. I have never seen this before and am a bit clueless as to what is the problem exactly. There is also an unchecked cast warning but that one is inevitable and unproblematic.

NB: From a design point of view I am not stuck since I can implement the delegation in the leaf Foo classes but, first,, that is less convenient (one more method in each class) and, second, I'd still like to understand the nature of the technical problem here.

[4451 byte] By [xolotla] at [2007-11-27 1:21:41]
# 1

Hi again,

your example is much too complicated to get the point. In fact, there is no co-variant return in your code, and that's the problem. Look at this:interface Type<T extends Tag> {

}

interface Tag {

Type<? extends Tag> getType();

}

interface FooTag extends Tag {

<T extends FooTag> Type<T> getType();

}

class MyFooTag implements FooTag {

public <T extends FooTag> Type<T> getType() {

return null;

}

}

MyFooTag will not compile, because it needs to implement Tag's getType(). The reason is that Type<FooTag> is no co-variant of Type<Tag>. Simple example:Type<FooTag> tft = ...;

Type<Tag> tt = tft; // bad

tt.setType(noFooTag);

If the second line was possible, you could assign a non-FooTag to the type, which originally only takes FooTags.

stefan.schulza at 2007-7-11 23:59:40 > top of Java-index,Core,Core APIs...
# 2

Indeed MyFooTag won't compile but the example is a bit contrived: since MyFooTag is a leaf class of the hierarchy (or so I assume), it does not make sense to leave the Tag type up in the air here. Thus I would have written

public Type<MyFooTag> getType()

which does compile, albeit with the following warning:

getType() in MyFooTag implements <T>getType() in FooTag return type requires unchecked conversion

and this raises another point that had bothered me since the first thread, namely that there is a difference between

<T extends FooTag> Type<T> getType();

and

Type<? extends FooTag> getType();

The former gives rise to the above warning whereas the latter compiles without a hitch. But I don't understand what the difference is; intuitively in both cases the type is effectively unspecified and thus both are wildcard of sorts.

NB: For those for haven't read the earlier threads, yes normally you'd use the wildcard form but there are circumstances in which you have to give the unspecified return type a dummy name(e.g.,when more than one type is involved in the signature and they are related).

> there is no co-variant return in your code

Then why doesn't the compiler complain about the overriding ("attempting to use incompatible return types", it would say)? AFAIK the only case where it is legal to override a method with the same parameters but a different return type is when there is covariance. Thus I think there is in fact covariance here.

xolotla at 2007-7-11 23:59:40 > top of Java-index,Core,Core APIs...