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.

Was it helpful?

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top