简体   繁体   中英

Java, parent static method returning child type

I'm new to java and Generics so please bear with me. I don't even know if this is possible. I've looked around and although there seem to be a few posts about this here and there I haven't found one that addresses my specific case clearly enough for me to understand what to do.

I basically have a static method in the parent class and would like it to return various types based on which child calls it. The trick here is that said child class that is returned also needs to be instantiated within the method.

Here's what I mean:

public class Parent {

   public String value;

   public Parent(String value)
   {
      this.value = value;
   }

   public static <T extends Parent> T load(DataStorage data, String key)
   {
      T inst = new ???(data.getValue(key));
      return inst;
   }
}

public class Child extends Parent {
}

Child child = Child.load(dataStore, "key"); // random dataStore instance

I'm not sure where to go from here. What do I use in place of ??? which should be whichever child (or Parent ) runs the load() ? Do I need to also do something along the lines of Child.<Child>load() ?

I'm open to alternative designs if you feel I'm mistaking in trying to do things like this. I don't like the idea of having to play around with Reflection in this situation (feels a little hacky)

Thanks in advance, I really appreciate it.

I guess what you want would be possible if Java didn't have type erasure and had 'constructor with parameter constraint' for generic types(like .net, but it has constraint for parameterless constructors only).

Maybe those two suits your needs:

If type of Child based on some selection criteria(eg an enumaration) I would go with a factory pattern like:

public class ParentFactory {
    Parent create(SomeEnum t, DataStore dataStore, String key) {
        switch (t) {
            case SomeEnum.Child1Related:
                return new Child1(dataStore.get(key));
            ...
        }
    }
}

But if creation is completely irrelevant(which is not in most cases), you can just define an init method in Parent and have initializer code there:

abstract class Parent {
    String value;
    Parent() {}
    protected void init(String s) { this.value = s; }
    static <T extends Parent> void initialize(DataStore data, String key, T t) {
        t.init(data.getValue(key));
}

Child c = new Child();
Parent.init(dataStore, key, c);

You can make init method as private if you want to prohibit childs to intercept that call.

Honestly, I favor first one much more. Second one is a little ugly :)

It sounds like the thing you're looking for is a strongly-typed key object, which can associate a string with the corresponding child type. What I've done in the past is simply write a class for this key type.

Assuming your datastore looks something like this:

public interface DataStore {
  DataItem get(String key);
}

And your various child classes look like this:

public final class Child1 extends Parent {
  public Child1(DataItem dataItem) {
    ...
  }
  ...
}

Your Key type could look like this:

/**
 * Represents a way to construct an object from a {@link DataItem}.
 *
 * @param <T> the type of the object to construct.
 */
public final class Key<T extends Parent> {
  private final String key;
  // Assuming Java 8 Function. If you're using Java 7 or older,
  // you can define your own Function interface similarly.
  private final Function<DataItem, T> factoryFunction;

  public Key(String key, Function<String, T> factoryFunction) {
    this.key = checkNotNull(key);
    this.factoryFunction = checkNotNull(factoryFunction);
  }

  public String key() {
    return this.key;
  }

  public T constructFrom(DataItem dataItem) {
    if (!key.equals(dataItem.getKey())) {
      throw new IllegalStateException(
          "DataItem is not valid for key " + key);
    }
    return factoryFunction.apply(dataItem);
  }
}

Then you'll probably want a collection of well-known keys:

/** Well-known {@link Key} instances. */
public final class Keys {
  private Keys() {}  // static class

  /** Key for {@link Child1}. */
  public static final Key<Child1> FIRST_CHILD
      = new Key<>("child1", Child1::new);

  /** Key for {@link Child2}. */
  public static final Key<Child2> SECOND_CHILD
      = new Key<>("child2", Child2::new);

  // etc.
}

Then you can define classes that work with these strongly-typed key instances:

public final class Loader {
  private final DataStore dataStore;

  public Loader(DataStore dataStore) {
    this.dataStore = checkNotNull(dataStore);
  }

  public <T extends Parent> T load(Key<T> dataKey) {
    return key.constructFrom(dataStore.get(dataKey.key()));
  }

  ...

}

Note that this example still works even if you don't have Java 8 -- you'll just need to use an anonymous inline class to construct the child, rather than a lambda expression:

public static final Key<Child1> FIRST_CHILD =
    new Key<Child1>("child1", new Function<DataItem, Child1>() {
      @Override public Child1 apply(DataItem dataItem) {
        return new Child1(dataItem);
      }
    });

You could of course use reflection for this part if you want, but manually writing the supplier functions will be faster. (Or, if you want the best of both worlds, you could use something like cglib's FastClass .) If you wanted to, you could also make the Key class abstract, so that you would subclass it and override a factory method rather than using a Function .

If you want to, you can merge the Loader type into your Parent class, but I wouldn't, since I think that would violate the Single Responsibility Principle -- typically you want the job of loading domain objects from storage to be separate from the domain objects themselves.

Hopefully that helps!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM