Recursive generic class definition and “cannot be converted” compilation error
Question
This is a design similar to other JPA BaseEntity patterns you may have seen:
@MappedSuperclass()
public abstract class Entity<X extends Entity<X>>
implements
Comparable<X>,
Serializable
{
private static final long serialVersionUID = 1L;
private Long id;
private Date timeStamp;
...
// Simply compare fields in subclass until difference is discovered
private int compareSubclassFields(X that)
{
int result = 0;
for(Comparator<X> comparator : getComparators())
{
result = comparator.compare(this,that); <<=== compilation error
if(result != 0) { break; }
}
return result;
}
/**
* Entity subclasses provide a list of their own special
* comparators that can contribute to the comparison.
*/
protected abstract List<Comparator<X>> getComparators();
}
Here is an example of a class that extends Entity:
public class User extends Entity<User>
{
private static final long serialVersionUID = 1L;
private String firstName;
private String lastName;
private String email;
...
@Override
public List<Comparator<User>> getComparators()
{
List<Comparator<User>> result =
new ArrayList<Comparator<User>>();
result.add(getLastNameComparator()); // Sort first on last name
result.add(getFirstNameComparator());// Next, by first name
result.add(getEmailComparator()); // Finally, by email (unique)
return result;
}
}
When I compile, I get the following error:
error: method compare in interface Comparator<T> cannot be
applied to given types;
result = comparator.compare(this,that);
^
required: X,X
found: Entity<X>,X
reason: actual argument Entity<X> cannot be converted to
X by method invocation conversion
where X,T are type-variables:
X extends Entity<X> declared in class Entity
T extends Object declared in interface Comparator
Reading Java Enum Definition, in particular the part where it says,
public class StatusCode extends Enum<StatusCode>
Now if you check the constraints, we've got Enum - so E=StatusCode. Let's check: does E extend Enum? Yes! We're okay.
I assume that in my example, where X extends Entity<X>
, 'this' would be an instance of User
and not Entity<User>
. Moreover, because Entity is an abstract class it must be extended and, therefore, compareNonIdFields
can only be invoked by an instance of X -- on itself. Of course, when I cast I get the unchecked warning:
warning: [unchecked] unchecked cast
result = comparator.compare(((X)this),that);
^
required: X
found: Entity<X>
where X is a type-variable:
X extends Entity<X> declared in class Entity
1 warning
Thoughts on why this recursive generic usage causes a compilation error and solutions to make the unchecked cast warning go away would be greatly appreciated.
Solution
You are writing this
keyword inside the Entity<X>
class. So,
this = Entity<X>
On the other hand, you provided Comparator
for X
, not for Entity<X>
.
You may keep a field to store related X
object inside Entity<X>
object and write in this manner:
result = comparator.compare(this.getX(),that);
OTHER TIPS
Imagine the following two classes.
class Foo extends Entity<Bar> {}
class Bar extends Entity<Foo> {}
Clearly the comparison can not only be invoked on instances of X
: If you invoke it on an instance of Foo
then X = Bar
and vice versa.
Edit: Just to be clear, while you intend to always substitute the inheriting type itself for X, this is not enforced by the language and/or the compiler. That's the source of your issue.
Weird! It's perfectly happy if you
result = comparator.compare((X)this, that);
So, are there any circumstances under which "this" might not be an X? Some odd permutation of subclassing and leaving the parameter unbound? Or further subclassing a subclass with a bound parameter?
A ha! It can happen if you subclass the class when X is already bound!
… no, that's not right. I gotta admit, I'm flummoxed.