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

1,00
р.
Нашла следующий порядок инициализации объекта для случая наследования классов:
Статические поля класса Parent Статический блок инициализации класса Parent Статические поля класса Сhild Статический блок инициализации класса Child Нестатические поля класса Parent Нестатический блок инициализации класса Parent Конструктор класса Parent Нестатические поля класса Сhild Нестатический блок инициализации класса Сhild Конструктор класса Сhild.
Но что делать, если несколько конструкторов и в родителе, и в наследнике - каким образом они загружаются? Все конструкторы родителя, а потом наследника или по одному в порядке написания?
На собеседовании мне показали пример возможной ошибки в конструкторе (или его исполнения - точно не помню) наследника из-за порядка конструкторов родителя, но я не могу найти нигде внятной информации и примеров такой ситуации в коде. Можете разъяснить этот момент?

Ответ
В классе-наследнике вызывается один конструктор родителя. Либо конструктор родителя вызывается явно в начале конструктора наследника с помощью конструкции 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.

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