Состояние Производного класса возражает когда переопределенный метод вызовов конструктора Базового класса в 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 происходит, потому что Производный объект наполовину инициализирован; подобный тому, что Jon Skeet говорит здесь

Мои вопросы:

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

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

Есть ли какие-либо варианты использования, где такое поведение желаемо?

12
задан Community 23 May 2017 в 12:24
поделиться

4 ответа

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

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

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

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

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

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

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

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

12
ответ дан 2 December 2019 в 07:01
поделиться

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

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

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

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

Существуют ли какие-либо варианты использования, когда такие желаемое поведение?

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

5
ответ дан 2 December 2019 в 07:01
поделиться

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

3
ответ дан 2 December 2019 в 07:01
поделиться

Есть некоторые свойства спецификации языка 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

2
ответ дан 2 December 2019 в 07:01
поделиться
Другие вопросы по тегам:

Похожие вопросы: