【问题标题】:How to create instance of subclass with constructor from super class如何使用超类的构造函数创建子类的实例
【发布时间】:2018-09-15 01:34:05
【问题描述】:

我想为作为超类的子类的类创建一个注册表。这些类存储在充当注册表的映射中。根据键从注册表中选择一个类,该类的实例将通过反射创建。

我想根据超类的构造函数(带有 1 个参数)实例化一个类。只有当我在子类中声明构造函数时它才有效。

有没有办法使用超类的构造函数来实例化类?有没有办法使该代码类型安全?

示例代码:

public class ReflectionTest {

    /**
     * Base class with no-args constructor and another constructor with 1 parameter
     */
    public static class BaseClass {

        Object object;

        public BaseClass() {
            System.out.println("Constructor with no args");
        }

        public BaseClass( Object object) {
            this.object = object;
            System.out.println("Constructor with parameter= " + object);
        }

        public String toString() {
            return "Object = " + object;
        }
    }

    /**
     * Subclass with 1 parameter constructor
     */
    public static class SubClass1 extends BaseClass {
        public SubClass1( Object object) {
            super(object);
        }
    }

    /**
     * Subclass with no-args constructor
     */
    public static class SubClass2 extends BaseClass {

    }

    public static void main(String[] args) {

        // registry for classes
        Map<Integer,Class<?>> registry = new HashMap<>();
        registry.put(0, SubClass1.class);
        registry.put(1, SubClass2.class);

        // iterate through classes and create instances
        for( Integer key: registry.keySet()) {

            // get class from registry
            Class<?> clazz = registry.get(key);

            try {

                // get constructor with parameter
                Constructor constructor = clazz.getDeclaredConstructor( Object.class);

                // instantiate class
                BaseClass instance = (BaseClass) constructor.newInstance(key);

                // logging
                System.out.println("Instance for key " + key + ", " + instance);

            } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                e.printStackTrace();
            }

        }

        System.exit(0);
    }
}

该示例给出以下控制台输出:

Constructor with parameter= 0
Instance for key 0, Object = 0
java.lang.NoSuchMethodException: swing.table.ReflectionTest$SubClass2.<init>(java.lang.Object)
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.getConstructor(Class.java:1825)
    at swing.table.ReflectionTest.main(ReflectionTest.java:63)

【问题讨论】:

  • 如果一个特定的子类同时具有零参数和一个参数的构造函数,你会怎么做?

标签: java


【解决方案1】:
  1. 超类不知道它的子类。
  2. 不继承构造函数。

因此,如果不对子类 ctor 做出假设,您就无法编写您想要的代码。

那你能做什么?使用抽象工厂模式。

我们可以创建一个interface Factory

@FunctionalInterface
public interface SuperclassFactory {
    Superclass newInstance(Object o);
}

您可以在 Factory 上创建多个方法,但这会降低 lambda 的简洁性。

现在你有一个Map&lt;Integer, SuperclassFactory&gt;,并填充它:

Map<Integer,SuperclassFactory> registry = new HashMap<>();
registry.put(0, SubClass1::new);
registry.put(1, SubClass2::new);

所以,为了使用这个Map,你只需这样做:

for(final Map.Entry<Integer,SuperclassFactory> e: registry.entrySet()) {
    //...
    final BaseClass instance = e.getValue().newInstance(e.getKey());
    //...
}

如果您的子类没有适当的 ctor,则此代码将无法编译,因为将没有可以使用的 ctor 引用。这是好事(TM)。为了使用当前的Subclass2 进行编译,您需要使用:

registry.put(1, obj -> new SubClass2());

所以现在我们有:

  1. 失去了反射
  2. 获得编译时类型安全
  3. 失去了丑陋的演员表(尽管那是通过滥用反射)

注意循环遍历 MapentrySet() 而不是 keySet()

【讨论】:

  • e.getValue().apply(e.getKey()) 必须是 e.getValue().newInstace(e.getKey())
  • 很好的答案,肯定以干净和安全的方式处理问题(我希望在 SO 上有更多这样的问题)。让我想起了this question
【解决方案2】:

构造函数需要在子类中显式定义。如果在超类中定义了构造函数,这并不意味着无论您是否使用反射,构造函数都可以用于创建子类的实例。

由于您的 SubClass2 没有带有一个参数的构造函数,因此当您尝试使用一个参数创建它的实例时,它会抛出 NoSuchMethodException。

【讨论】:

    【解决方案3】:
    • 使用clazz.getDeclaredConstructors()获取类的所有构造函数;
    • 遍历它们以找到最适用的构造函数,例如如果单对象参数构造函数不可用,则选择零参数;
    • 使用适当的参数调用该构造函数。

    这不可能是完全安全的,因为您无法提前知道给定类是否存在任何适用的公共构造函数,例如ctor 可能是私有的,或者可用的构造函数可能不接受您想要的类型的参数(例如,需要一个字符串,但您只有一个对象)。

    【讨论】:

    • 有人可能会争辩说,凭借反射的魔力,private 不是障碍。但那样疯狂就在……
    【解决方案4】:

    你正在使用这一行来获取构造函数

    clazz.getDeclaredConstructor( Object.class);
    

    但是您的 Subclass2 没有单个参数构造函数,因此会引发异常。

    改用 clazz.getDeclaredConstructors() 方法并根据参数计数调用构造函数。

                BaseClass instance;
                // get constructor with parameter
                Constructor constructor = clazz.getDeclaredConstructors()[0];
                if (constructor.getParameterCount() == 1) {
                    instance = (BaseClass) constructor.newInstance(key);
                } else {
                    instance = (BaseClass) constructor.newInstance();
                }  
    

    【讨论】:

    • 如果我有一个接受String 的演员和一个接受number 的演员怎么办?您需要检查这些参数的类型
    • 这不是他的代码中的问题吧? BaseClass instance = (BaseClass) constructor.newInstance(key);他想创建一个显式传递参数的实例。如果他想传递一个字符串,他不会在构造函数中传递 KEY。
    • 当然,不是这个确切的例子。但是 OP 正在寻找一个通用的解决方案——我确信 OP 完全有能力以某种方式解决这个问题。该问题要求一般而言如何做到这一点 - 这不是一个完整的答案。
    猜你喜欢
    • 2018-09-25
    • 2017-11-24
    • 1970-01-01
    • 1970-01-01
    • 2020-07-27
    • 1970-01-01
    • 2016-03-02
    • 2019-05-29
    • 1970-01-01
    相关资源
    最近更新 更多