Привет! В прошлый раз мы говорили о конструкторах, и узнали о них достаточно много. Сейчас мы поговорим о такой вещи, как конструкторы базовых классов.
Что такое базовый класс? Дело в том, что в Java несколько разных классов могут иметь общее происхождение.
Это называется наследованием. У нескольких классов-потомков может быть один общий класс-предок. Например, представим что у нас есть класс Animal (животное):
public class Animal {

   String name;
   int age;
}
Мы можем создать для него, например, 2 класса-потомка — Cat и Dog. Это делается с использованием ключевого слова extends.
public class Cat extends Animal {

}

public class Dog extends Animal {

}
Это может нам пригодиться в будущем. Например, если будет задача ловить мышей — создадим в программе объект Cat. Если задача бегать за палочкой — тут мы используем объект Dog. А если будем создавать программу, симулирующую ветеринарную клинику — она будет работать с классом Animal (чтобы уметь лечить и кошек, и собак). Очень важно запомнить на будущее, что при создании объекта в первую очередь вызывается конструктор его базового класса, а только потом — конструктор самого класса, объект которого мы создаем. То есть при создании объекта Cat сначала отработает конструктор класса Animal, а только потом конструктор Cat. Чтобы убедиться в этом — добавим в конструкторы Cat и Animal вывод в консоль.
public class Animal {

   public Animal() {
       System.out.println("Отработал конструктор Animal");
   }
}


public class Cat extends Animal {

   public Cat() {
       System.out.println("Отработал конструктор Cat!");
   }

   public static void main(String[] args) {
       Cat cat = new Cat();
   }
}
Вывод в консоль: Отработал конструктор Animal Отработал конструктор Cat! Действительно, все так и работает! Для чего это нужно? Например, чтобы не дублировать общие поля двух классов. Например, у каждого животного есть сердце и мозг, но не у каждого есть хвост. Мы можем объявить общие для всех животных поля brain и heart в родительском классе Animal, а поле tail — в подклассе Cat. Теперь мы создадим конструктор для класса Cat, куда передадим все 3 поля.
public class Cat extends Animal {

   String tail;

   public Cat(String brain, String heart, String tail) {
       this.brain = brain;
       this.heart = heart;
       this.tail = tail;
   }

   public static void main(String[] args) {
       Cat cat = new Cat("Мозг", "Сердце", "Хвост");
   }
}
Обрати внимание: конструктор успешно работает, хотя в классе Cat нет полей brain и heart. Эти поля “подтянулись” из базового класса Animal. У класса-наследника есть доступ к полям базового класса, поэтому в нашем классе Cat они видны. Поэтому нам не нужно в классе Cat дублировать эти поля — мы можем взять их из класса Animal. Более того, мы можем явно вызвать конструктор базового класса в конструкторе класса-потомка. Базовый класс еще называют “суперклассом”, поэтому в Java для его обозначения используется ключевое слово super. В предыдущем примере
public Cat(String brain, String heart, String tail) {
       this.brain = brain;
       this.heart = heart;
       this.tail = tail;
   }
Мы отдельно присваивали каждое поле, которое есть в нашем родительском классе. На самом деле этого можно не делать. Достаточно вызвать конструктор родительского класса и передать ему нужные параметры:
public class Animal {

   String brain;
   String heart;

   public Animal(String brain, String heart) {
       this.brain = brain;
       this.heart = heart;
   }

public class Cat extends Animal {

   String tail;

   public Cat(String brain, String heart, String tail) {
       super(brain, heart);
       this.tail = tail;
   }

   public static void main(String[] args) {
       Cat cat = new Cat("Мозг", "Сердце", "Хвост");
   }
}
В конструкторе Cat мы вызвали конструктор Animal и передали в него два поля. Нам осталось явно проинициализировать только одно поле — tail, которого в Animal нет. Помнишь, мы говорили о том, что при создании объекта в первую очередь вызывается конструктор класса-родителя? Так вот, именно поэтому слово super() всегда должно стоять в конструкторе первым! Иначе логика работы конструкторов будет нарушена и программа выдаст ошибку.
public class Cat extends Animal {

   String tail;

   public Cat(String brain, String heart, String tail) {
       this.tail = tail;
       super(brain, heart);//ошибка!
   }

   public static void main(String[] args) {
       Cat cat = new Cat("Мозг", "Сердце", "Хвост");
   }
}
Компилятор знает, что при создании объекта класса-потомка сначала вызывается конструктор базового класса. И если ты попытаешься вручную изменить это поведение - он не позволит этого сделать.

Процесс создания объекта.

Выше мы с тобой рассмотрели пример с базовым и родительским классом — Animal и Cat. Теперь на примере этих двух классов мы рассмотрим процесс создания объекта и инициализации переменных. Мы знаем, что переменные бывают статическими и переменными экземпляра (нестатическими). Также мы знаем, что в базовом классе Animal есть свои переменные, а в классе-потомке Cat — свои. Добавим к классу Animal и Cat по одной статической переменной для наглядности. Переменная animalCount в классе Animal будет означать общее число видов животных на земле, а переменная catsCount — число видов семейства кошачьих. Кроме того, присвоим всем нестатическим переменным у обоих классов стартовые значения (которое потом изменится в конструкторе).
public class Animal {

   String brain = "Изначальное значение brain в классе Animal";
   String heart = "Изначальное значение heart в классе Animal";

   public static int animalCount = 7700000;

   public Animal(String brain, String heart) {
       System.out.println("Выполняется конструктор базового класса Animal");
       System.out.println("Были ли уже проинициализированы переменные класса Animal?");
       System.out.println("Текущее значение статической переменной animalCount = " + animalCount);
       System.out.println("Текущее значение brain в классе Animal = " + this.brain);
       System.out.println("Текущее значение heart в классе Animal = " + this.heart);
       System.out.println("Были ли уже проинициализированы переменные класса Cat?");
       System.out.println("Текущее значение статической переменной catsCount = " + Cat.catsCount);

       this.brain = brain;
       this.heart = heart;
       System.out.println("Конструктор базового класса Animal завершил работу!");
       System.out.println("Текущее значение brain = " + this.brain);
       System.out.println("Текущее значение heart = " + this.heart);
   }
}

public class Cat extends Animal {

   String tail = "Изначальное значение tail в классе Cat";

   static int catsCount = 37;

   public Cat(String brain, String heart, String tail) {
       super(brain, heart);
       System.out.println("Конструктор класса Cat начал работу (конструктор Animal уже был выполнен)");
       System.out.println("Текущее значение статической переменной catsCount = " + catsCount);
       System.out.println("Текущее значение tail = " + this.tail);
       this.tail = tail;
       System.out.println("Текущее значение tail = " + this.tail);
   }

   public static void main(String[] args) {
       Cat cat = new Cat("Мозг", "Сердце", "Хвост");
   }
}
Итак, мы создаем новый объект класса Cat, унаследованного от Animal. Добавим в нашу программу подробный консольный вывод, чтобы посмотреть что и в каком порядке будет происходить. Вот что будет выведено в консоль в результате создания объекта Cat: Выполняется конструктор базового класса Animal Были ли уже проинициализированы переменные класса Animal? Текущее значение статической переменной animalCount = 7700000 Текущее значение brain в классе Animal = Изначальное значение brain в классе Animal Текущее значение heart в классе Animal = Изначальное значение heart в классе Animal Были ли уже проинициализированы переменные класса Cat? Текущее значение статической переменной catsCount = 37 Конструктор базового класса Animal завершил работу! Текущее значение brain = Мозг Текущее значение heart = Сердце Конструктор класса Cat начал работу (конструктор Animal уже был выполнен) Текущее значение статической переменной catsCount = 37 Текущее значение tail = Изначальное значение tail в классе Cat Текущее значение tail = Хвост Итак, теперь мы наглядно видим в каком порядке происходит инициализация переменных и вызов конструкторов при создании нового объекта:
  1. Инициализируются статические переменные базового класса (Animal). В нашем случае — переменной animalCount класса Animal присваивается значение 7700000.

  2. Инициализируются статические переменные класса-потомка (Cat). Обрати внимание — мы все еще внутри конструктора Animal, а в консоли уже написано:

    Выполняется конструктор базового класса Animal

    Были ли уже проинициализированы переменные класса Animal?

    Текущее значение статической переменной animalCount = 7700000

    Текущее значение brain в классе Animal = Изначальное значение brain в классе Animal

    Текущее значение heart в классе Animal = Изначальное значение heart в классе Animal

    Были ли уже проинициализированы переменные класса Cat?

    Текущее значение статической переменной catsCount = 37


  3. Дальше инициализируются нестатические переменные базового класса. Мы специально присвоили им первоначальные значения, которые потом в конструкторе меняются на новые. Конструктор Animal еще не отработал до конца, но первоначальные значения brain и heart уже присвоены:

    Выполняется конструктор базового класса Animal

    Были ли уже проинициализированы переменные класса Animal?

    Текущее значение статической переменной animalCount = 7700000

    Текущее значение brain в классе Animal = Изначальное значение brain в классе Animal

    Текущее значение heart в классе Animal = Изначальное значение heart в классе Animal


  4. Начинает работу конструктор базового класса.

    В том, что этот этап идет только четвертым по счету, мы уже убедились: в первых трех пунктах на момент начала работы конструктора Animal многим переменным уже присвоены значения.


  5. Инициализация нестатических полей дочернего класса (Cat).

    Она происходит раньше, чем конструктор Cat начинает работу.

    На момент, когда он начал работу, у переменной tail уже было значение:

    Конструктор класса Cat начал работу (конструктор Animal уже был выполнен)
    Текущее значение статической переменной catsCount = 37
    Текущее значение tail = Изначальное значение tail в классе Cat


  6. Вызывается конструктор класса потомка Cat

    Вот так выглядит процесс создания объекта в Java!

    Надо сказать, что мы не большие поклонники зубрежки, но порядок инициализации переменных и вызова конструкторов лучше выучить наизусть и запомнить на будущее.

    Это сильно увеличит твое понимание хода работы программы, и состояния твоих объектов в каждый конкретный момент.

    Тем более, что часто у классов нет прямых родительских классов (суперклассов/ базовых классов). В таком случае пункты, связанные с базовым классом, не будут выполняться.