如何使用超类中的构造函数创建子类的实例

问题描述 投票:14回答:4

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

我想根据超类的构造函数(带有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
4个回答
12
投票
  1. 超类不知道它的孩子。
  2. 构造函数不是继承的。

因此,在不对子类ctor进行假设的情况下,您无法编写所需的代码。

所以,你可以做什么?使用抽象工厂模式。

我们可以创建一个interface Factory

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

你可以在Factory上创建多个方法,但这会使lambda不那么整洁。

现在你有一个Map<Integer, SuperclassFactory>,并填充它:

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引用。这是Good Thing(TM)。要使用当前的Subclass2进行编译,您需要使用:

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

所以现在我们有:

  1. 失去了反思
  2. 获取编译时类型安全性
  3. 失去了丑陋的演员阵容(尽管这是通过滥用反射)

注:循环通过MapentrySet()而不是它的keySet()


1
投票

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

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


1
投票
  • 使用clazz.getDeclaredConstructors()获取该类的所有构造函数;
  • 迭代它们以找到最适用的构造函数,例如:如果单个Object-arg构造函数不可用,则选择零参数;
  • 使用适当的参数调用该构造函数。

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


0
投票

您正在使用此行来获取构造函数

clazz.getDeclaredConstructor( Object.class);

但是你的Subclass2没有一个参数构造函数,因此引发了异常。

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

            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();
            }  
© www.soinside.com 2019 - 2024. All rights reserved.