简体   繁体   English

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

[英]How to create instance of subclass with constructor from super class

I'd like to create a registry for classes which are subclasses of a super class. 我想为类是超类的子类创建一个注册表。 The classes are stored in a map which acts as registry. 这些类存储在充当注册表的映射中。 A class is picked from the registry depending on a key and an instance of that class will be created via reflection. 根据键,从注册表中选择一个类,并通过反射创建该类的实例。

I'd like to instantiate a class depending on a constructor (with 1 parameter) of the super class. 我想根据超类的构造函数(带有1个参数)来实例化一个类。 It works only if I declare the constructor in the subclasses as well. 它只有在我在子类中声明构造函数时才有效。

Is there a way to instantiate the class using a constructor of a super class? 有没有办法使用超类的构造函数实例化类? Is there a way to make that code type-safe? 有没有办法使代码类型安全?

Example code: 示例代码:

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

The example gives the following console output: 该示例提供以下控制台输出:

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)
  1. A superclass has no knowledge of its children. 超类不知道它的孩子。
  2. Constructors are not inherited. 构造函数不是继承的。

Therefore, without making assumptions about the subclass ctor, you cannot write the code that you want. 因此,在不对子类ctor进行假设的情况下,您无法编写所需的代码。

So what can you do? 所以,你可以做什么? Use an Abstract Factory pattern. 使用抽象工厂模式。

We can create an interface Factory : 我们可以创建一个interface Factory

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

You could create more than one method on the Factory , but that would make it less neat for lambdas. 你可以在Factory上创建多个方法,但这会使lambda不那么整洁。

Now you have a Map<Integer, SuperclassFactory> , and populate it: 现在你有一个Map<Integer, SuperclassFactory> ,并填充它:

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

So, in order to use this Map you simply do: 因此,为了使用此Map您只需执行以下操作:

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

If your subclass does not have the appropriate ctor, this code will not compile as there will be no ctor reference that can be used. 如果您的子类没有相应的ctor,则此代码将无法编译,因为不会有可用的ctor引用。 This is Good Thing (TM). 这是Good Thing(TM)。 In order to compile with the current Subclass2 you would need to use: 要使用当前的Subclass2进行编译,您需要使用:

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

So now we have: 所以现在我们有:

  1. Lost the reflection 失去了反思
  2. Acquired compile time type safety 获取编译时类型安全性
  3. Lost the ugly cast (although that was through misuse of reflection) 失去了丑陋的演员阵容(尽管这是通过滥用反射)

NB loop through a Map 's entrySet() not its keySet() . NB循环通过MapentrySet()而不是它的keySet()

Constructors need to be defined explicitly in the subclass. 需要在子类中显式定义构造函数。 If a constructor is defined in superclass that doesn't mean that constructor can be used to create an instance of subclass whether you are using reflection or not. 如果构造函数在超类中定义,并不意味着无论是否使用反射,都可以使用构造函数创建子类的实例。

Since your SubClass2 doesn't have constructor with one argument, so when you try to create an instance of it with one argument, it's throwing NoSuchMethodException. 由于您的SubClass2没有带有一个参数的构造函数,因此当您尝试使用一个参数创建它的实例时,它会抛出NoSuchMethodException。

  • Use clazz.getDeclaredConstructors() to get all constructors of the class; 使用clazz.getDeclaredConstructors()获取类的所有构造函数;
  • Iterate through them to find the best applicable constructor, eg pick the zero-args one if a single-Object-arg constructor is not available; 迭代它们以找到最适用的构造函数,例如,如果单个Object-arg构造函数不可用,则选择零参数;
  • Invoke that constructor using the appropriate parameters. 使用适当的参数调用该构造函数。

There is no way for this to be entirely safe, since you can't know in advance whether any applicable public constructors exist for a given class, eg the ctor might be private, or the available constructors might not accept parameters of the type you want (eg needs a String, but you only have an Object). 由于您无法预先知道给定类是否存在任何适用的公共构造函数,例如ctor可能是私有的,或者可用的构造函数可能不接受您想要的类型的参数,因此无法完全安全。 (例如需要一个String,但你只有一个Object)。

You are using this line to get the constructor 您正在使用此行来获取构造函数

clazz.getDeclaredConstructor( Object.class);

But your Subclass2 does not have a single argument constructor hence the exception is thrown. 但是你的Subclass2没有一个参数构造函数,因此引发了异常。

Use clazz.getDeclaredConstructors() method instead and invoke constructor based on parameters count. 请改用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();
            }  

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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