When called on a derived class, have a generic method defined in base class return derived class type in Java

I have a utility class for interacting with the Datastore (GAE's in-built Datastore in my case) and it has methods like:

//Class GaeDataUtil
public static <T> Optional<Key<T>> saveEntity(T entity)

(Optional is from the Guava library and Key<T> from Objectify, although I doubt any of this makes a difference.)

I want my (minimal) hierarchy of entities to have a .save() method. So that for:

public class User extends RootEntity

where RootEntity provides:

public Optional<Key<T>> save() {
    //Skipping the error-handling.
    return GaeDataUtil.saveEntity(this);
}

I can write:

User myUser = new User();
// set some properties
Optional<Key<User>> optKey = myUser.save();

But of course that doesn't work because a call to myUser.save() returns Optional<Key<RootEntity>> not Optional<Key<User>> as I want.

I can avoid this issue by typecasting in User.save() (and Account.save() and Project.save() etc. etc.) and suppressing warnings, but even if there are only (say) 10 entity classes extending RootEntity, that's still a fair bit of boilerplate code to write just to typecast. Also, I think that much of the benefit of having a class hierarchy is lost if I have to write code (however minimal) for every derived class (there will be other, similar methods too).

Is there a better solution to this?

Update: using Java 7.

3 Answers
  1. You will just need to type cast it to the Generic type T in the RootEntity.save() method.

    public <T> Optional<Key<T>> save() {
        //Skipping the error-handling.
        return (Optional<Key<T>> GaeDataUtil.saveEntity(this); // This line will generate a warning.
    }
    

    And then when you write,

    Optional<Key<User>> optKey = myUser.save();
    

    It will automatically be inferred correctly because of Target Type Inference.

    2015-11-17 03:48:23
  2. One solution is to parameterize RootEntity something like this:

    class RootEntity<Subclass extends RootEntity> {
      public Optional<Key<Subclass>> save() {...}
    }
    

    Then define your subclass like:

    class User extends RootEntity<User> {...}
    

    I've used this pattern before. If there is a slicker solution, I'll be eager to see it. :)

    Eric Simonton2015-11-16 18:58:23
  3. This is what finally worked:

    public <T extends RootEntity> Optional<Key<T>> save1() {
        @SuppressWarnings("unchecked")
        Key<T> key = (Key<T>) ofy().save().entity(this).now();
        return Optional.fromNullable(key);
    }
    

    Doing this in two steps works (get the Key, then wrap it up in an Optional) --- it let's the Target Type Inference work correctly. Doing it in a single step doesn't:

    public <T extends RootEntity> Optional<Key<T>> save2() {
        return (Optional<Key<T>>) Optional.fromNullable(ofy().save().entity(this).now());
    }
    

    This second form as suggested by @Codebender shows an error (Cannot cast from Optional<Key<RootEntity>> to Optional<Key<T>>), not a warning in Eclipse.

    When called on a derived class, have a generic method defined in base class return derived class type in Java

    However, the basic idea by @Codebender of using Target Type Inference was sound.

    markvgti2015-11-17 05:24:48
Related Articles
You Might Also Like