Как создать экземпляр подкласса с конструктором из суперкласса

Я хотел бы создать реестр для классов, которые являются подклассами суперкласса. Классы хранятся на карте, которая действует как реестр. Класс выбирается из реестра в зависимости от ключа, и экземпляр этого класса будет создан посредством отражения.

Я хотел бы создать экземпляр класса в зависимости от конструктора (с 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)

Ответы

Ответ 1

  1. Суперкласс не знает своих детей.
  2. Конструкторы не наследуются.

Поэтому, не делая предположений о подклассе ctor, вы не можете написать код, который вы хотите.

Так что ты можешь сделать? Используйте шаблон абстрактной фабрики.

Мы можем создать interface Factory:

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

На Factory вы можете создать несколько методов, но это сделает его менее аккуратным для лямбда.

Теперь у вас есть 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. Потерял уродливый литой (хотя это было из-за неправильного использования рефлексии)

NB петля через Map entrySet() не его keySet().

Ответ 2

Конструкторы должны быть явно определены в подклассе. Если конструктор определен в суперклассе, это не означает, что конструктор может использоваться для создания экземпляра подкласса, независимо от того, используете ли вы отражение или нет.

Поскольку ваш SubClass2 не имеет конструктора с одним аргументом, поэтому, когда вы пытаетесь создать экземпляр его с одним аргументом, он бросает NoSuchMethodException.

Ответ 3

  • Используйте clazz.getDeclaredConstructors() чтобы получить все конструкторы класса;
  • Проведите через них, чтобы найти подходящий конструктор, например, выберите нуль-args, если конструктор с одним объектом-аргументом недоступен;
  • Вызовите этот конструктор, используя соответствующие параметры.

Нет никакого способа для этого быть полностью безопасным, поскольку вы не можете заранее знать, существуют ли применимые публичные конструкторы для данного класса, например, ctor может быть закрытым, или доступные конструкторы могут не принимать параметры типа, который вы хотите (например, требуется строка, но у вас есть только объект).

Ответ 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();
            }