【问题标题】:Java, parent static method returning child typeJava,父静态方法返回子类型
【发布时间】:2015-09-07 17:39:09
【问题描述】:

我是 java 和泛型的新手,所以请多多包涵。我什至不知道这是否可能。我环顾四周,虽然这里和那里似乎有一些关于此的帖子,但我还没有找到一个足以清楚地解决我的具体情况的帖子,让我了解该怎么做。

我基本上在父类中有一个静态方法,并希望它根据子类调用它返回各种类型。这里的技巧是返回的子类也需要在方法中实例化。

这就是我的意思:

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

我不知道从这里去哪里。我应该用什么来代替 ??? 哪个应该是运行 load() 的孩子(或 Parent)?我是否还需要按照Child.&lt;Child&gt;load() 的方式做一些事情?

如果您觉得我尝试做这样的事情有误,我愿意接受替代设计。我不喜欢在这种情况下不得不玩反射的想法(感觉有点老套)

在此先感谢,我真的很感激。

【问题讨论】:

  • 只有一种方法可以替换您的???:您需要传递对象的类并使用反射。看到这个问题:stackoverflow.com/questions/1090458/…
  • 谢谢,我看到了,因为我有构造函数参数,它最终变得有点混乱。我觉得不需要使用反射,是否有替代设计?
  • 1. T 有一个上限(父级)但没有下限,所以你不能用任何东西代替 ???不引发编译器警告。除了,正如@Tunaki 所说,传递一个 Class 参数。
  • (抱歉,我不知道 Enter 键 == 提交...刚刚赢得了一些评论的声誉:D)。 2. Child.load(...)等价于Parent.load(...),没办法给它不同的类型参数。
  • 谢谢我忽略了我在示例中运行负载而没有参数的事实。所以本质上,它要么是反射,要么什么都没有? @Tunaki 引用的帖子提到了工厂接口。可能会看看这对我是否会更好

标签: java generics


【解决方案1】:

我猜如果 Java 没有类型擦除并且对泛型类型有“带参数约束的构造函数”(如 .net,但它只对无参数构造函数有约束),你想要的可能是可能的。

也许这两个适合您的需求:

如果 Child 的类型基于某些选择标准(例如枚举),我会使用工厂模式,例如:

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

但是如果创建是完全不相关的(在大多数情况下不是),你可以在 Parent 中定义一个 init 方法并在那里有初始化代码:

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);

如果您想禁止孩子拦截该调用,您可以将 init 方法设为私有。

老实说,我更喜欢第一个。第二个有点丑:)

【讨论】:

  • 谢谢,我实际上可能会放弃您的 Factory 建议并研究 Builder。我会再等一下,看看是否有人有建议,如果没有,我会将其标记为正确。
【解决方案2】:

听起来您正在寻找的是一个强类型的键对象,它可以将字符串与相应的子类型相关联。我过去所做的只是为这种键类型编写一个类。

假设您的数据存储区如下所示:

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

您的各种子类如下所示:

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

您的Key 类型可能如下所示:

/**
 * 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);
  }
}

那么您可能需要一组知名键:

/** 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.
}

然后您可以定义使用这些强类型键实例的类:

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()));
  }

  ...

}

请注意,即使您没有 Java 8,此示例仍然有效——您只需要使用匿名内联类来构造子类,而不是 lambda 表达式:

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);
      }
    });

如果您愿意,当然可以在这部分使用反射,但手动编写供应商函数会更快。 (或者,如果你想要两全其美,你可以使用像 cglib 的 FastClass 这样的东西。)如果你愿意,你也可以使 Key 类抽象,这样你就可以继承它并覆盖工厂方法而不是使用Function

如果你愿意,你可以将Loader 类型合并到你的Parent 类中,但我不会,因为我认为这会违反Single Responsibility Principle——通常你想要加载域对象的工作从存储与域对象本身分开。

希望对您有所帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多