Состояние объекта производного класса при вызове конструктором базового класса переопределенного метода в Java

пожалуйста, обратитесь к коду Java ниже:

class Base{
     Base(){
         System.out.println("Base Constructor");
         method();
     }
     void method(){}    
}

class Derived extends Base{
    int var = 2;
    Derived(){
         System.out.println("Derived Constructor");  
    }

     @Override
     void method(){
        System.out.println("var = "+var);
     }
 }

class Test2{
    public static void main(String[] args) {
        Derived b = new Derived();
    }
}

выход виден:

Base Constructor
var = 0
Derived Constructor

Я думаю, что var = 0 происходит, потому что производный объект наполовину инициализирован; похоже на то, что Джон Скит говорит здесь

мои вопросы:

почему переопределенный метод вызывается, если объект производного класса еще не сформирован?

в какой момент времени var присваивается значение 0?

есть ли случаи, когда такое поведение желательно?

4 ответов


  • на Derived объект и был создан-просто конструктор еще не запущен. Тип объекта никогда не изменяется в Java после момента его создания, что происходит до запуска всех конструкторов.

  • var присваивается значение по умолчанию 0 как часть процесса создания объекта перед запуском конструкторов. В принципе, ссылка на тип получает set, а остальная часть памяти, представляющая объект стирается до нуля (концептуально, во всяком случае - возможно, он уже был стерт до нуля раньше, как часть сборки мусора)

  • это поведение, по крайней мере, приводит к последовательности, но это может быть боль. С точки зрения согласованности предположим, что у вас есть подкласс только для чтения изменяемого базового класса. Базовый класс может иметь isMutable() свойство, которое фактически было по умолчанию true-но подкласс переопределил его, чтобы всегда возвращать false. Было бы странно, если бы объект был изменяемым перед запуском конструктора подкласса, но неизменяемый впоследствии. С другой стороны, это наверняка странно в ситуациях, когда вы в конечном итоге запускаете код в классе до запуска конструктора для этого класса: (

несколько рекомендаций:

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

  • очень старайтесь избегать вызовов не-окончательных методов во время конструктора - это, скорее всего, вызовет путаницу. Документ любой метод вызывает вас действительно есть сделать очень ясно, так что любой, переопределяющий их, знает, что они будут вызваны до инициализации законченный.

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

  • старайтесь не злоупотреблять наследованием в первую очередь - это станет проблемой только тогда, когда у вас есть подкласс, происходящий от суперкласса, отличного от Object :) проектирование для наследования сложно.


почему переопределенный метод GET вызывается, если объект производного класса еще не создан?

Derived конструктор класса неявно называет Base конструктор класса в качестве первой инструкции. Base конструктор класса называет method() который вызывает переопределенное утверждение в Derived класс, потому что это класс, объект которого создается. method() на Derived класс видит var как 0 в этой точке.

в какой момент времени назначается var значение 0?

var присваивается значение по умолчанию для int введите т. е. 0 перед контруктором Derived класса вызывается. Ему присваивается значение 2 после неявный вызов суперкласса contructor завершен и до заявления Derived конструктор класса начинает выполнение.

есть ли случаи, когда такие поведение желанной?

вообще плохая идея использовать non -final неprivate методы в конструкторах/инициализаторы неfinal класса. Причины очевидны в вашем коде. Если создаваемый объект является экземпляром подкласса, методы могут дать неожиданные результаты.


обратите внимание, что это отличается от C++, где тип изменяется во время построения объекта, так что вызов виртуального метода из конструкторов базового класса не вызывает переопределение производного класса. То же самое происходит в обратном процессе уничтожения. Так это может быть небольшая ловушка для C++ программисты на Java.


есть некоторые свойства спецификации языка Java, которые следует отметить, чтобы объяснить это поведение:

  • конструктор суперкласса всегда неявно / явно вызывается перед конструктором подкласса.
  • вызов метода из конструктора похож на любой другой вызов метода; если метод не является окончательным, то вызов является виртуальным вызовом, что означает, что реализация метода для вызова связана с типом среды выполнения объект.
  • перед выполнением конструктора все элементы данных автоматически инициализируются значениями по умолчанию (0 для числовых примитивов, null для объектов, false для логического).

последовательность событий выглядит следующим образом:

  1. создается экземпляр подкласса
  2. все данные-члены инициализируются значениями по умолчанию
  3. конструктор вызывается сразу передает управление в суперкласс' конструктор.
  4. супер конструктор инициализирует некоторые/все собственные элементы данных, а затем вызывает виртуальный метод.
  5. метод переопределяется подклассом, поэтому вызывается реализация подкласса.
  6. метод пытается использовать элементы данных подкласса, предполагая, что они уже инициализированы, но это не так - стек вызовов еще не вернулся в конструктор подкласса.

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

http://www.javaspecialists.eu/archive/Issue086.html

http://www.javaspecialists.eu/archive/Issue086b.html