Есть ли способ инициализировать переменные-члены подкласса в Java перед вызовом конструктора суперкласса? - PullRequest
9 голосов
/ 27 марта 2010

Мне это нужно, потому что конструктор в суперклассе вызывает метод, который переопределяется в подклассе. Метод возвращает значение, которое передается конструктору подкласса. Но конструктор суперкласса должен вызываться до конструктора подкласса, поэтому у меня нет шансов сохранить переданное значение.

Ответы [ 5 ]

17 голосов
/ 27 марта 2010

Вызов переопределенного метода из конструктора суперкласса просто не будет работать - не делайте этого. Конструктор суперкласса всегда должен заканчиваться раньше, чем у подкласса. Во время выполнения конструктора суперкласса рассматриваемый объект является (частично инициализированным) экземпляром суперкласса, а не подклассом! Поэтому, если вы попытаетесь вызвать какую-либо переопределенную функцию из конструктора, поля подкласса, от которых она может зависеть, еще не инициализированы (как вы заметили). Это фундаментальный факт проектирования класса, и обходного пути нет.

Как объяснено в Effective Java 2nd. Ред. (глава 4, пункт 17):

Существуют [...] ограничения, которым должен подчиняться класс, чтобы наследование. Конструкторы не должны вызывать переопределяемые методы напрямую или косвенно. Если вы нарушите это правило, произойдет сбой программы. Конструктор суперкласса выполняется перед конструктором подкласса, поэтому переопределяющий метод в подклассе будет вызван перед подклассом конструктор побежал. Если переопределяющий метод зависит от какой-либо инициализации выполняется конструктором подкласса, метод не будет вести себя как ожидается.

Если вы можете изменить реализацию суперкласса, попробуйте переместить вызов виртуальной функции из конструктора. Одним из способов достижения этого является использование фабричного метода:

class Super {
    public void init() { ... }
}

class Subclass extends Super {
    private Subclass() { super(); ... }
    public void init() { super.init(); ... }
    public static Subclass createInstance() {
        Subclass instance = new Subclass();
        instance.init();
        return instance;
    }
}

Обратите внимание, что конструктор Subclass является закрытым, чтобы гарантировать, что он может быть создан только через createInstance(), таким образом, экземпляры всегда инициализируются должным образом. OTOH это также предотвращает дальнейшее создание подклассов. Тем не менее, подклассы конкретного класса в любом случае не рекомендуется - класс, предназначенный для подкласса, должен быть абстрактным (с конструктором protected в данном случае). И, конечно же, любые последующие подклассы также должны иметь непубличные конструкторы и статические фабричные методы, которые старательно вызывают init() ...

6 голосов
/ 27 марта 2010

Ваш вопрос обобщен следующим фрагментом:

abstract class Base {
    Base() {
        dontdoit();
    }
    abstract void dontdoit();
}
public class Child extends Base {
    final int x;
    Child(int x) {
        this.x = x;
    }
    @Override void dontdoit() {
        System.out.println(x);
    }

    public static void main(String args[]) {
        new Child(42); // prints "0" instead of "42"!!!
    }
}

Это цитата Джоша Блока и Нила Гафтера Java Puzzlers: ловушки, ловушки и угловые случаи :

Проблема возникает всякий раз, когда конструктор вызывает метод, который был переопределен в его подклассе. Метод, вызываемый таким образом, всегда запускается до инициализации экземпляра, когда его объявленные поля все еще имеют значения по умолчанию. Чтобы избежать этой проблемы, никогда не вызывает переопределенные методы из конструкторов , прямо или косвенно [EJ Item 15]. Этот запрет распространяется на инициализаторы экземпляров и тела псевдоконструкторов readObject и clone. (Эти методы называются псевдоконструкторами, поскольку они создают объекты без вызова конструктора.)

Короче говоря: НЕ ДЕЛАЙТЕ ЭТОГО!

Кроме того, в книге предлагается возможное решение (слегка отредактированное для общности):

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

1 голос
/ 04 апреля 2010

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

public abstract class Base {
        public Base(int value) {
                init(value);
                System.out.println(getValue());
        }

        public abstract void init(int value);
        public abstract int getValue();
}

public class Derived extends Base {
        private int value;

        public Derived(int value) {
                super(value);
        }
        public void init(int value) {
                this.value = value;
        }
        public int getValue() {
                return value;
        }
        public static void main(String[] argv) {
                new Derived(25);
        }
}

> Производная Java
25

0 голосов
/ 27 марта 2010

Если это проблема, у вас есть фундаментальная проблема в вашей иерархии классов; Суперклассы не могут знать ни о каком конкретном подклассе, потому что их может быть несколько, они могут даже загружаться динамически во время выполнения. Вот почему любые вызовы super () в конструкторе должны быть в первой строке.

Как правило, не вызывайте открытые методы в конструкторе.

0 голосов
/ 27 марта 2010

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

Решение 1:

Использовать статическую фабрику с синхронизированным.Это может выглядеть (сокращенно):

private static String savedValue;

public static Abc create(String value){
  synchronized(Abc.class){
    savedValue = value;
    Abc abc = new Abc();
    savedValue = null;
    return abc;
  }

private Abc(){
}

private value;

String getValue(){
  if( value == null ){
    value = savedValue;
  }
  return value;
}

Решение 2:

Сделайте различие в вашем методе, если собственный конструктор уже был запущен с флагом.Затем вам нужно скопировать некоторые вещи супер-конструктора с реальным значением.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...