Пользователь Professor Hans Noodles
Professor Hans Noodles
41 уровень

Конструкторы базовых классов

Статья из группы Java Developer
Привет! В прошлый раз мы говорили о конструкторах, и узнали о них достаточно много. Сейчас мы поговорим о такой вещи, как конструкторы базовых классов. Конструкторы базовых классов   - 1Что такое базовый класс? Дело в том, что в Java несколько разных классов могут иметь общее происхождение. Конструкторы базовых классов   - 2Это называется наследованием. У нескольких классов-потомков может быть один общий класс-предок. Например, представим что у нас есть класс 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! Конструкторы базовых классов   - 3Надо сказать, что мы не большие поклонники зубрежки, но порядок инициализации переменных и вызова конструкторов лучше выучить наизусть и запомнить на будущее. Это сильно увеличит твое понимание хода работы программы, и состояния твоих объектов в каждый конкретный момент. Тем более, что часто у классов нет прямых родительских классов (суперклассов/ базовых классов). В таком случае пункты, связанные с базовым классом, не будут выполняться.
Комментарии (213)
Чтобы просмотреть все комментарии или оставить свой,
перейдите в полную версию
Mike-L 17 уровень
15 мая 2021
проще посмотреть адекватное видео от Головача линк про механику объекта и как правильно подметили Вот что нужно зазубрить: 1. статические переменные базового класса 2. статические переменные класса-потомка 3. нестатические переменные базового класса 4. конструктор базового класса 5. нестатических полей дочернего класса 6. конструктор класса потомка
Jack Daniel 12 уровень, Минск
4 мая 2021
"...Выше мы с тобой рассмотрели пример с базовым и родительским классом — Animal и Cat. ...". Видимо, это пример "напутанности", о которых упоминали предыдущие комментаторы. Необходимо внести исправление, вроде: "...рассмотрели пример с родительским и дочерним классами...". Родительский класс = базовый класс = суперкласс = наследуемый класс – класс, от которого наследуются классы-потомки. Дочерний класс = подкласс = класс-потомок = наследующий класс – класс, который наследует информацию и свойства от класса-родителя.
Павел 13 уровень
23 апреля 2021
Увы, но как по мне, то пример в статье перегружен. Где-то на лекциях ранее был код подобный ниже, где просто и понятно можно наблюдать порядок инициализации переменных, выполнение конструкторов.

public class Main {
    public static void main(String[] args) {
        new ClassB(); //создаем объект потомка
    }

    public static String print(String text) {
        System.out.println(text);
        return text;
    }

    //класс родитель
    public static class ClassA {
        static String a = Main.print("Родитель: Инициализация static а");
        String b = Main.print("Родитель: Инициализация b");
        String c = Main.print("Родитель: Инициализация c");

        public ClassA() {
            Main.print("Родитель: Выполнение конструктора ClassA");
        }
    }

    //класс потомок
    public static class ClassB extends ClassA {
        static String d = Main.print("Потомок: Инициализация static d");
        String e = Main.print("Потомок: Инициализация e");
        String f = Main.print("Потомок: Инициализация f");

        public ClassB() {
            Main.print("Потомок: Выполнение конструктора ClassB");
        }
    }
}
Вывод в консоль:

Родитель: Инициализация static а
Потомок: Инициализация static d
Родитель: Инициализация b
Родитель: Инициализация c
Родитель: Выполнение конструктора ClassA
Потомок: Инициализация e
Потомок: Инициализация f
Потомок: Выполнение конструктора ClassB
Dima_Sever 14 уровень
21 апреля 2021
Отличная статья.
Oleksii 27 уровень, Харьков
18 апреля 2021

public class Animal {

    String brain = "Значение переменной brain на этапе 3 равно BRAIN";
    static String countAnimals = "Значение переменной countAnimals на этапе 1 равно 7 млн";

    Animal(String brain) {
        System.out.println("Этап 4. Старт этапа. Начал работу конструктор суперкласса");
        System.out.println("Этап 1 выполнен? Инициирована ли уже статическая переменная суперкласса на этапе 1? " + countAnimals);
        System.out.println("Этап 2 выполнен? Инициирована ли уже статическая переменная дочернего класса на этапе 2? " + Cat.countCats);
        System.out.println("Этап 3 выполнен? Инициирована ли уже нестатическая переменная суперкласса на этапе 3? " + this.brain);
        System.out.println("Этап 4. Финиш этапа. Закончил работу конструктор суперкласса");
    }
}
    class Cat extends Animal{

    public static String countCats = "Значение переменной countCats на этапе 2 равно 37";
    String tail = "Значение переменной tail на этапе 5 равно TAIL";

        Cat(String brain, String tail) {
            super(brain);
            System.out.println("Этап 6. Старт этапа. Начал работу конструктор дочернего класса");
            System.out.println("Этап 6. Текущее значение нестатической переменной суперкласса brain = " + brain);
            System.out.println("Этап 5 выполнен? Инициирована ли уже нестатическая переменная дочернего класса? Переменная tail = " + this.tail);
            this.tail = tail;
            System.out.println("Этап 6. Обновлена ли уже нестатическая переменная дочернего класса на этапе 6? Переменная tail = " + this.tail);
            System.out.println("Этап 6. Финиш этапа. Закончил работу конструктор дочернего класса");
        }

        public static void main(String[] args) {
                Cat cat = new Cat("МОЗГ из main", "ХВОСТ из main");
        }
    }
Andrew Nikitin 17 уровень, Санкт-Петербург
12 апреля 2021
Класс.Информативно. Спасибо. было бы неплохо после слов: "...Мы можем объявить общие для всех животных поля brain и heart в родительском классе Animal, а поле tail — в подклассе Cat...""" добавить: public class Animal { String brain; String heart; } Просто если бегло читать или опираться на блоки кода можно залипнуть , поскольку ранее таких полей не наблюдалось. С уважением..
Дмитрий Коляскин 10 уровень, Москва
11 апреля 2021
Статья запутанная просто жесть, особенно где объяснение про то что как когда работает это просто ужас, ничего не понятно.!!!
Vitaliy Shlyakhetko 15 уровень, Львов
10 апреля 2021
Как- то много текста в println конструктора... Правильно ли понимаю: 1) инициализация статической переменной родител класса; 2) инициализация всех нестатических полей родител класса; 3) инициализация статической переменной дочернего класса; 4) инициализация всех нестатических полей дочернего класса; Вроде так? а третий и второй этап можна поменять местами? Разве не все статические переменные одновременно иницилизируются? Хотя это наверное и без разницы? и кому не трудно, поставьте лайк для достижения) Спасибо.
9 апреля 2021
Не знаю, писали ли, но кажется в статье забыли сделать +1 для animalCount и catCount. Мы ведь вроде как создали одного кота. Работе программы не мешает, но
Foreteller 35 уровень, Липецк
7 апреля 2021
Народ, кому не трудно, поставьте лайк для достижения) Спасибо.