Состояние объекта производного класса при вызове конструктором базового класса переопределенного метода в 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 для логического).
последовательность событий выглядит следующим образом:
- создается экземпляр подкласса
- все данные-члены инициализируются значениями по умолчанию
- конструктор вызывается сразу передает управление в суперкласс' конструктор.
- супер конструктор инициализирует некоторые/все собственные элементы данных, а затем вызывает виртуальный метод.
- метод переопределяется подклассом, поэтому вызывается реализация подкласса.
- метод пытается использовать элементы данных подкласса, предполагая, что они уже инициализированы, но это не так - стек вызовов еще не вернулся в конструктор подкласса.
короче говоря, всякий раз, когда конструктор суперкласс вызывает не окончательный метод, у нас есть потенциальный риск попасть в эту ловушку, поэтому делать это не рекомендуется. Обратите внимание, что нет элегантного решения, если вы настаиваете на этом шаблоне. Вот 2 сложных и творческих, требующих синхронизации потоков (!):