Incompatible types when using recursive generics in Java

Niels Basjes :

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.

ernest_k :

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).

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=90884&siteId=1