Reputation: 10642
I have written java code where I use a recursive form of generics to achieve a clean way of making a Builder pattern inheritable.
This works but I do not understand some of the warnings and errors I get from the java compiler.
This is the severely simplified version of the part I don't understand:
package nl.basjes.test;
public class Foo<X extends Foo<X>> {
public X doSomething() {
return this;
}
}
For the "return this;" I get the error
Incompatible Types
Required: X
Found : nl.basjes.test.Foo <X>
Now 'this' is always a subclass of Foo (or even Foo itself) and 'X' is defined as X extends Foo<X>
.
To my knowledge these should be "the same" but apparently they are not.
So in my code I added a cast to the return statement like this:
package nl.basjes.test;
public class Foo<X extends Foo<X>> {
public X doSomething() {
return (X)this;
}
}
which makes the code compile and work as expected and intended.
I do however still get a warning about "Unchecked cast" for the same reason as above (but now it is just a warning).
$ javac -Xlint:unchecked nl/basjes/test/Foo.java
nl/basjes/test/Foo.java:5: warning: [unchecked] unchecked cast
return (X)this;
^
required: X
found: Foo<X>
where X is a type-variable:
X extends Foo<X> declared in class Foo
1 warning
Why doesn't Java see that X
(which extends Foo<X>
) and this
(which extends Foo<X>
) are compatible?
At this point my best guess is that this has to do with a part of the type erasure that I do not understand yet.
Upvotes: 3
Views: 163
Reputation: 45309
When you consider the concrete type arguments, it becomes easier to see the problem:
Suppose
Foo<Bar> barFoo = ...;
When you call barFoo.doSomething()
, you expect to get a Bar
object:
Bar bar = barFoo.doSomething()
However, your actual implementation:
public X doSomething() {
return this;
}
Can roughly be filled with the following concrete parameters:
public Bar doSomething() {
return this; //But "this" is a Foo<Bar>, not a Bar.
}
Here's a different example to make it even more obvious:
class Bar extends Foo<Bar> {
}
class Baz extends Foo<Bar> { //note this is a Foo<Bar>
}
And:
Baz baz = new Baz();
Bar bar = baz.doSomething();
In the above, you expect baz.doSomething()
to return a Bar
, but the code in doSomething()
is returning a Baz
, but casting it to Bar
, which has a type safety problem (in fact, these types are incompatible, but you only get a classcastexception when you have different classes as in the last example).
Upvotes: 5