Why does this code not compile?
public class x
{
private void test()
{
handle(new ThingA());
handle(new ModifiedThingA());
}
private <T extends BaseThing<T>, X extends T> java.util.List<T> handle(X object)
{
return object.getList();
}
private static class BaseThing<T extends BaseThing<T>>
{
public java.util.List<T> getList()
{
return null;
}
}
private static class ThingA
extends BaseThing<ThingA>
{
}
private static class ModifiedThingA
extends ThingA
{
}
}
Java 6 gives this this error in handle(new ModifiedThingA());
:
x.java:6: <T,X>handle(X) in x cannot be applied to (x.ModifiedThingA)
handle(new ModifiedThingA());
^
Java 7 does not even like handle(new ThingA());
, this is the Java 7 output:
x.java:5: error: invalid inferred types for T; inferred type does not conform to declared bound(s)
handle(new ThingA());
^
inferred: ThingA
bound(s): CAP#1
where T,X are type-variables:
T extends BaseThing<T> declared in method <T,X>handle(X)
X extends T declared in method <T,X>handle(X)
where CAP#1 is a fresh type-variable:
CAP#1 extends BaseThing<CAP#1> from capture of ?
x.java:6: error: invalid inferred types for T; inferred type does not conform to declared bound(s)
handle(new ModifiedThingA());
^
inferred: ModifiedThingA
bound(s): CAP#1
where T,X are type-variables:
T extends BaseThing<T> declared in method <T,X>handle(X)
X extends T declared in method <T,X>handle(X)
where CAP#1 is a fresh type-variable:
CAP#1 extends BaseThing<CAP#1> from capture of ?
2 errors
It seems to me that javac
is mistaking ModifiedThingA
for a BaseThing<ModifiedThingA>
when it is in fact a BaseThing<ThingA>
. Is this my bug or javac
's?
javac
's behavior appears to be correct. Theoretically one type variable, T
, would be sufficient. However, you introduced a second type variable, X
, to help type inference along. The argument for X
gets inferred first and then, based on the invocation context, the argument for T
is inferred:
List<ThingA> a = handle(new ThingA());
List<ThingA> b = handle(new ModifiedThingA());
However, your invocation context does not put any bounds on the return type. Thus the compiler is forced to introduce a type variable (CAP#1
) with the null type as lower bound. T
's argument will be inferred as glb(BaseThing<CAP#1>) = BaseThing<CAP#1>
. Given its lower bound, X
is not provably a subtype of T
.
There are two or three ways out of this.
T
from its bounds argumentI prefer option 3:
private <T extends BaseThing<T>> List<T> handle(BaseThing<? extends T> object) {
return new ArrayList<T>(object.getList());
// or (using guava's ImmutableList)
return ImmutableList.copyOf(object.getList());
}
Happy generics.
Your code compiles fine in javac 1.8.0_45 and Eclipse 4.1.1.
Probably the changes that where made to the type inference algorithm in Java 8 in order to compile lambda expressions in a nice way also solved your problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With