См. 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 происходит, потому что Производный объект наполовину инициализирован; подобный тому, что Jon Skeet говорит здесь
Мои вопросы:
Почему переопределенный метод становится названным, если объект Производного класса еще не создается?
В каком моменте времени var присваивают значение 0?
Есть ли какие-либо варианты использования, где такое поведение желаемо?
Объект Derived
уже создан - просто конструктор еще не запущен. Тип объекта никогда не меняется в Java после момента его создания, которое происходит до запуска всех конструкторов.
var
присваивается значение по умолчанию 0 как часть процесса создания объекта, до запуска конструкторов. По сути, ссылка на тип устанавливается, а остальная память, представляющая объект, очищается до нуля (концептуально, во всяком случае - возможно, она уже была очищена до нуля ранее, как часть сборки мусора)
Такое поведение, по крайней мере, приводит к согласованности, но может быть и неприятным. С точки зрения согласованности, предположим, что у вас есть подкласс базового класса, доступного только для чтения. У базового класса может быть свойство isMutable()
, которое по умолчанию имеет значение true, но подкласс переопределил его, чтобы оно всегда возвращало false. Было бы странно, если бы объект был изменяемым до запуска конструктора подкласса, но неизменяемым после этого. С другой стороны, это определенно странно в ситуациях, когда вы выполняете код в классе до запуска конструктора этого класса :(
Несколько рекомендаций:
Старайтесь не делать много работы в конструкторе. Один из способов избежать этого - выполнить работу в статическом методе, а затем сделать заключительную часть статического метода вызовом конструктора, который просто устанавливает поля. Конечно, это означает, что вы не получите преимуществ полиморфизма во время выполнения работы - но делать это в вызове конструктора в любом случае было бы опасно.
Очень старайтесь избегать вызовов нефинальных методов в конструкторе - это может привести к путанице. Документируйте все вызовы методов, которые вы действительно должны сделать очень четко, чтобы любой переопределяющий их знал, что они будут вызваны до завершения инициализации.
Если вы должны вызвать метод во время конструирования, то обычно не стоит вызывать его после этого. Если это так, задокументируйте это и постарайтесь указать это в имени.
Старайтесь не злоупотреблять наследованием в первую очередь - это станет проблемой только тогда, когда у вас есть подкласс, производный от суперкласса, отличного от Object :) Проектирование наследования - дело непростое.
Почему переопределенный метод получает вызывается, если объект производного класса еще не создан?
Конструктор производного класса
неявно вызывает конструктор класса Base
в качестве первого оператора. Конструктор базового
класса вызывает метод ()
, который вызывает переопределенную реализацию в классе Derived
, потому что это класс, объект которого создается. method ()
в Производный класс
видит var
как 0 в этот момент.
В какой момент времени назначается var значение 0?
var
присваивается значение по умолчанию для типа int
, то есть 0 перед вызовом конструктора класса Derived
. Ему присваивается значение 2 после неявного вызова конструктора суперкласса и до начала выполнения операторов в конструкторе класса Derived
.
Существуют ли какие-либо варианты использования, когда такие желаемое поведение?
Как правило, использование не final
не частных
методов в конструкторах / инициализаторах не- final
класс. Причины очевидны в вашем коде. Если создаваемый объект является экземпляром подкласса, методы могут дать неожиданные результаты.
Обратите внимание, что это отличается от C++, где тип меняется во время конструирования объекта, так что вызов виртуального метода из конструкторов базового класса не вызывает переопределение производного класса. То же самое происходит и в обратном порядке при разрушении. Так что это может стать небольшой ловушкой для программистов C++, приходящих в Java.
Есть некоторые свойства спецификации языка Java, которые следует отметить для объяснения такого поведения:
Последовательность событий следующая:
Короче говоря, всякий раз, когда конструктор суперкласса вызывает не финальный метод, у нас есть потенциальный риск попасть в эту ловушку, поэтому делать это не рекомендуется. Обратите внимание: если вы настаиваете на этом шаблоне, элегантного решения не существует. Вот 2 сложных и творческих, требующих синхронизации потоков (!):