1

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.

markvgti
  • 4,321
  • 7
  • 40
  • 62
  • This sounds a lot like the use of recursive generics in the Builder pattern - see http://stackoverflow.com/questions/21086417/builder-pattern-and-inheritance – Andy Turner Nov 16 '15 at 13:51
  • My hierarchy is currently very shallow (and likely to stay that way): a `RootEntity` and a number of derived entity classes under that. – markvgti Nov 16 '15 at 13:56
  • You need to typecast (and suppress unchecked) only in `RootEntity` class. Not all the subclasses. – Codebender Nov 16 '15 at 14:02
  • @Codebender how would I know which class to typecast to? – markvgti Nov 16 '15 at 14:04
  • @AndyTurner I changed to `public abstract class RootEntity>` and added `public Optional> save1() { return (Optional>) GaeDataUtil.saveEntity(this); }` but Eclipse says "Cannot cast from Optional>> to Optional>" – markvgti Nov 16 '15 at 14:13

3 Answers3

2

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.

Community
  • 1
  • 1
Codebender
  • 14,221
  • 7
  • 48
  • 85
1

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 Simonton
  • 5,702
  • 2
  • 37
  • 54
1

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.

Target Type Inference works if done in two steps

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

markvgti
  • 4,321
  • 7
  • 40
  • 62
  • With your original `static` method `saveEntity`, there was a restriction on `T` as it has to be compatible with the type of the `entity` parameter. With your new `save1` instance method, there is no restriction on `T`, the caller can infer *anything* for `T` without getting a compiler warning. In other words, your method promises to return an `Optional>` which it can’t hold. In the programming world, we call such a design a fatal accident waiting to happen… – Holger Nov 17 '15 at 14:41