Страницы

Поиск по вопросам

пятница, 10 января 2020 г.

Порядок инициализации класса (конструкторов) родителя и наследника при нескольких конструкторах в обоих классах

#java #конструктор


Нашла следующий порядок инициализации объекта для случая наследования классов:


Статические поля класса Parent;
Статический блок инициализации класса Parent;
Статические поля класса Сhild;
Статический блок инициализации класса Child;
Нестатические поля  класса Parent;
Нестатический блок инициализации класса Parent;
Конструктор класса Parent;
Нестатические поля  класса Сhild;
Нестатический блок инициализации класса Сhild
Конструктор класса Сhild.


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

На собеседовании мне показали пример возможной ошибки в конструкторе (или его исполнения
- точно не помню) наследника из-за порядка конструкторов родителя, но я не могу найти
нигде внятной информации и примеров такой ситуации в коде. Можете разъяснить этот момент?
    


Ответы

Ответ 1



В классе-наследнике вызывается один конструктор родителя. Либо конструктор родителя вызывается явно в начале конструктора наследника с помощью конструкции super, либо вызывается неявно при подстановке super(); в начало конструктора компилятором. При этом если у родителя нет конструктора без аргументов, то код приведёт к ошибке компиляции. Документация по этому поводу. Таким образом, нет неопределённости с тем, сколько конструкторов родителя и какие именно вызываются в потомке. Проблемы из-за порядка вызова конструкторов и инициализации полей классов могут возникнуть, как писал @zRrr, если в конструкторе родителя вызывается переопределяемый или реализуемый потомком метод, который в свою очередь использует ещё не инициализированные поля потомка. Пример: public abstract class Animal { private final String fullName; protected Animal() { fullName = "Animal " + getName(); } public String getFullName() { return fullName; } protected abstract String getName(); } public class Dog extends Animal { private String thisName = "DDog"; protected String getName() { return thisName; } } public static void main(String[] args) { Animal animal = new Dog(); System.out.println(animal.getFullName()); } На экран будет выведено Animal null, так как на момент вызова метода getName у Dog поле thisName ещё не инициализировано. При создании animal происходит вызов конструктора по умолчанию у Dog, который приводит сначала к вызову конструктора без параметров у Animal, а только затем к инициализации поля thisName. Ещё есть такой пример, взятый из этой статьи: public class Upper { String upperString; public Upper() { Initializer.initialize(this); } } public class Lower extends Upper { String lowerString = null; public Lower() { super(); System.out.println("Upper: " + upperString); System.out.println("Lower: " + lowerString); } public static void main(final String[] args) { new Lower(); } } public class Initializer { static void initialize(final Upper anUpper) { if (anUpper instanceof Lower) { Lower lower = (Lower)anUpper; lower.lowerString = "lowerInited"; } anUpper.upperString = "upperInited"; } } На экран будет выведено: Upper: upperInited Lower: null; Но если заменить String lowerString = null; на String lowerString;, то вывод будет: Upper: upperInited Lower: lowerInited Проблема в первоначальном варианте в том, что присваивание null в String lowerString = null; происходит после вызова конструктора родителя, в котором в свою очередь вызывается метод Initializer.initialize. То есть в lowerString сначала записывается "lowerInited", а затем null. Оба примера демонстрируют проблемы, возникающие из-за неправильных предположений о порядке выполнения кода. Сложность осознания того, что за чем выполняется в данном коде, сама по себе уже говорит о том, что на практике код так писать не стоит.

Ответ 2



Думаю такой порядок: /** * Порядок инициализации таков: * [1]. Статические поля базового класса; * [2]. Статический блок инициализации базового класса; * [3]. Статические поля производного класса; * [4]. Статический блок инициализации производного класса; * [5]. Поля (Глобальные переменные) базового класса; * [6]. Нестатический блок инициализации базового класса; * [7]. Конструктор базового класса [если у родителя нет конструктора без аргументов, то код приведёт к ошибке компиляции]; * [8]. Поля (Глобальные переменные) производного класса; * [9]. Нестатический блок инициализации производного класса; * [10]. Конструктор производного класса; */ class Base { //[1] transient переменные и статические поля не сериализуются private static Base instance = new Base("))(("); //BASE = 0 // public static int _baseStaticInsect = 111; //сокрыто, доступ Base._baseStaticBase private static final int DELTA = 2; private static int BASE = 3; //[2] static { System.out.println("[static instance class initializer] " + Inherit.class.getSimpleName()); } //[5] private int i = 5; protected int j; //mod:protected-internal int x; int internal; //mod:package public int iInc; //[6] { System.out.println("[instance class initializer] " + this.getClass().getSimpleName()); } //[7] //private недоступен из вне, нельзя создать без рефлексии //если у нет конструктора без аргументов или он приватный, то код приведёт к ошибке компиляции]; public Base() { System.out.println("[constructor] " + this.getClass().getSimpleName()); x = BASE + DELTA; } //[7] public Base(String data) { this(); System.out.println("[constructor] (String)" + this.getClass().getSimpleName()); System.out.println(data); } static int printInit(String s) { return -1; } } public class Inherit extends Base { //[3] public static int _baseStaticBase = 222; public static int _baseStaticInherit = 4; //[4] static { System.out.println("[static class initializer] " + Inherit.class.getSimpleName()); } //[8] private int internal; public int Integnal() { //getInternal return this.internal; } private boolean internalFlag; public boolean isInternalFlag() { return internalFlag; } //[9] { System.out.println("[instance class initializer] Inherit " + this.getClass().getSimpleName()); } //[10] public Inherit() { this("start"); System.out.println("Inherit constructor"); } //[10] public Inherit(String data) { System.out.println("Inherit constructor data"); } public static void main(String[] args) { Base base = new Base(); Inherit inherit = new Inherit(); } }

Комментариев нет:

Отправить комментарий