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

Зачем нужен конструктор?

Статья из группы Java Developer
Привет! Сегодня мы разберем очень важную тему, которая касается наших объектов. Тут без преувеличения можно сказать, что этими знаниями ты будешь пользоваться каждый день в реальной работе! Мы поговорим о конструкторах. Ты, возможно, слышишь этот термин впервые, но на самом деле наверняка пользовался конструкторами, только сам не замечал этого :) Мы убедимся в этом позже.

Что такое конструктор в Java и зачем он нужен?

Рассмотрим два примера.

public class Car {

   String model;
   int maxSpeed;

   public static void main(String[] args) {

       Car bugatti = new Car();
       bugatti.model = "Bugatti Veyron";
       bugatti.maxSpeed = 407;

   }
}
Мы создали наш автомобиль и установили для него модель и максимальную скорость. Однако в реальном проекте у объекта Car явно будет не 2 поля. А, например, 16 полей!

public class Car {

   String model;//модель
   int maxSpeed;//максимальная скорость
   int wheels;//ширина дисков
   double engineVolume;//объем двигателя
   String color;//цвет
   int yearOfIssue;//год выпуска
   String ownerFirstName;//имя владельца
   String ownerLastName;//фамилия владельца
   long price;//цена
   boolean isNew;//новая или нет
   int placesInTheSalon;//число мест в салоне
   String salonMaterial;//материал салона
   boolean insurance;//застрахована ли
   String manufacturerCountry;//страна-производитель
   int trunkVolume;//объем багажника
   int accelerationTo100km;//разгон до 100 км/час в секундах


   public static void main(String[] args) {
       Car bugatti = new Car();

       bugatti.color = "blue";
       bugatti.accelerationTo100km = 3;
       bugatti.engineVolume = 6.3;
       bugatti.manufacturerCountry = "Italy";
       bugatti.ownerFirstName = "Amigo";
       bugatti.yearOfIssue = 2016;
       bugatti.insurance = true;
       bugatti.price = 2000000;
       bugatti.isNew = false;
       bugatti.placesInTheSalon = 2;
       bugatti.maxSpeed = 407;
       bugatti.model = "Bugatti Veyron";

   }

}
Мы создали новый объект Car. Одна проблема: полей-то у нас 16, а проинициализировали мы только 12! Попробуй сейчас по коду найти те, которые мы забыли! Не так-то просто, да? В такой ситуации программист может легко ошибиться и пропустить инициализацию какого-то поля. В итоге поведение программы станет ошибочным:

public class Car {

   String model;//модель
   int maxSpeed;//максимальная скорость
   int wheels;//ширина дисков
   double engineVolume;//объем двигателя
   String color;//цвет
   int yearOfIssue;//год выпуска
   String ownerFirstName;//имя владельца
   String ownerLastName;//фамилия владельца
   long price;//цена
   boolean isNew;//новая или нет
   int placesInTheSalon;//число мест в салоне
   String salonMaterial;//материал салона
   boolean insurance;//застрахована ли
   String manufacturerCountry;//страна-производитель
   int trunkVolume;//объем багажника
   int accelerationTo100km;//разгон до 100 км/час в секундах


   public static void main(String[] args) {
       Car bugatti = new Car();

       bugatti.color = "blue";
       bugatti.accelerationTo100km = 3;
       bugatti.engineVolume = 6.3;
       bugatti.manufacturerCountry = "Italy";
       bugatti.ownerFirstName = "Amigo";
       bugatti.yearOfIssue = 2016;
       bugatti.insurance = true;
       bugatti.price = 2000000;
       bugatti.isNew = false;
       bugatti.placesInTheSalon = 2;
       bugatti.maxSpeed = 407;
       bugatti.model = "Bugatti Veyron";

       System.out.println("Модель Bugatti Veyron. Объем двигателя - " + bugatti.engineVolume + ", багажника - " + bugatti.trunkVolume + ", салон сделан из " + bugatti.salonMaterial +
       ", ширина дисков - " + bugatti.wheels + ". Была приоберетена в 2018 году господином " + bugatti.ownerLastName);

   }

}
Вывод в консоль:
Модель Bugatti Veyron. Объем двигателя — 6.3, багажника — 0, салон сделан из null, ширина дисков — 0. Была приобретена в 2018 году господином null
Вашему покупателю, отдавшему 2 миллиона долларов за машину, явно не понравится, что его назвали “господином null”! А если серьезно, в итоге в нашей программе оказался некорректно созданный объект — машина с шириной дисков 0 (то есть вообще без дисков), отсутствующим багажником, салоном, сделанным из неизвестного материала, да еще и принадлежащая непонятно кому. Можно только представить, как такая ошибка может “выстрелить” при работе программы! Нам нужно как-то избежать подобных ситуаций. Надо, чтобы в нашей программе было ограничение: при создании нового объекта машины для него всегда должны быть указаны, например, модель и максимальная скорость. Иначе — не позволять создание объекта. С этой задачей легко справляются функции-конструкторы. Они получили свое название не просто так. Конструктор создает своеобразный “каркас” класса, которому каждый новый объект класса должен соответствовать. Давай для удобства вернемся к более простому варианту класса Car с двумя полями. С учетом наших требований, конструктор для класса Car будет выглядеть так:

public Car(String model, int maxSpeed) {
   this.model = model;
   this.maxSpeed = maxSpeed;
}
А создание объекта теперь выглядит так:

public static void main(String[] args) {
   Car bugatti = new Car("Bugatti Veyron", 407);
}
Обрати внимание, как создается конструктор. Он похож на обычный метод, но у него нет типа возвращаемого значения. При этом в конструкторе указывается название класса, тоже с большой буквы. В нашем случае — Car. Кроме того, в конструкторе используется новое для тебя ключевое слово this. "this" по-английски — "этот, этого". Это слово указывает на конкретный предмет. Код в конструкторе:

public Car(String model, int maxSpeed) {
   this.model = model;
   this.maxSpeed = maxSpeed;
}
можно перевести почти дословно: "model для этой машины (которую мы сейчас создаем) = аргументу model, который указан в конструкторе. maxSpeed для этой машины (которую мы создаем) = аргументу maxSpeed, который указан в конструкторе." Так и произошло:

public class Car {

   String model;
   int maxSpeed;

   public Car(String model, int maxSpeed) {
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   public static void main(String[] args) {
       Car bugatti = new Car("Bugatti Veyron", 407);
       System.out.println(bugatti.model);
       System.out.println(bugatti.maxSpeed);
   }

}
Вывод в консоль:
Bugatti Veyron 407
Конструктор успешно присвоил нужные значения. Ты, возможно, заметил, что конструктор очень похож на обычный метод! Так оно и есть: конструктор — это метод, только немного специфичный :) Так же как в метод, в наш конструктор мы передали параметры. И так же как вызов метода, вызов конструктора не сработает, если их не указать:

public class Car {

   String model;
   int maxSpeed;

   public Car(String model, int maxSpeed) {
       this.model = model;
       this.maxSpeed = maxSpeed;
   }

   public static void main(String[] args) {
       Car bugatti = new Car(); //ошибка!
   }
  
}
Видишь, конструктор сделал то, чего мы пытались добиться. Теперь нельзя создать машину без скорости или без модели! На этом сходство конструкторов и методов не заканчивается. Так же, как и методы, конструкторы можно перегружать. Представь, что у тебя дома живут 2 кота. Одного из них ты взял еще котенком, а второго ты принес домой с улицы уже взрослым и не знаешь точно, сколько ему лет. Значит, наша программа должна уметь создавать котов двух видов — с именем и возрастом для первого кота, и только с именем — для второго кота. Для этого мы перегрузим конструктор:

public class Cat {

   String name;
   int age;

   //для первого кота
   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   //для второго кота
   public Cat(String name) {
       this.name = name;
   }

   public static void main(String[] args) {

       Cat barsik = new Cat("Barsik", 5);
       Cat streetCatNamedBob = new Cat("Bob");
   }

}
К изначальному конструктору с параметрами “имя” и “возраст” мы добавили еще один, только с именем. Точно так же мы перегружали методы в прошлых уроках. Теперь мы успешно можем создать оба варианта котов :) Зачем нужны конструкторы? - 2Помнишь, в начале лекции мы говорили, что ты уже пользовался конструкторами, только сам не замечал этого? Так и есть. Дело в том, что у каждого класса в Java есть так называемый конструктор по умолчанию. У него нет никаких аргументов, но он срабатывает каждый раз при создании любого объекта любого класса.

public class Cat {

   public static void main(String[] args) {

       Cat barsik = new Cat(); //вот здесь сработал конструктор по умолчанию
   }
}
На первый взгляд это незаметно. Ну создали объект и создали, где тут работа конструктора? Чтобы это увидеть, давай прямо руками напишем для класса Cat пустой конструктор, а внутри него выведем какую-нибудь фразу в консоль. Если она выведется, значит конструктор отработал.

public class Cat {

   public Cat() {
       System.out.println("Создали кота!");
   }

   public static void main(String[] args) {

       Cat barsik = new Cat(); //вот здесь сработал конструктор по умолчанию
   }
}
Вывод в консоль:
Создали кота!
Вот и подтверждение! Конструктор по умолчанию всегда незримо присутствует в твоих классах. Но тебе нужно знать еще одну его особенность. Дефолтный конструктор исчезает из класса, когда ты создаешь какой-то конструктор с аргументами. Доказательство этого, на самом деле, мы уже видели выше. Вот в этом коде:

public class Cat {

   String name;
   int age;

   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   public static void main(String[] args) {

       Cat barsik = new Cat(); //ошибка!
   }
}
Мы не смогли создать кота без имени и возраста, потому что определили конструктор для Cat: строка + число. Дефолтный конструктор сразу после этого исчез из класса. Поэтому обязательно запомни: если тебе в твоем классе нужно несколько конструкторов, включая пустой, его нужно создать отдельно. Например, мы создаем программу для ветеринарной клиники. Наша клиника хочет делать добрые дела и помогать бездомным котикам, про которых мы не знаем ни имени, ни возраста. Тогда наш код должен выглядеть так:

public class Cat {

   String name;
   int age;

   //для домашних котов
   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   //для уличных котов
   public Cat() {
   }

   public static void main(String[] args) {

       Cat barsik = new Cat("Barsik", 5);
       Cat streetCat = new Cat();
   }
}
Теперь, когда мы явно прописали конструктор по умолчанию, мы можем создавать котов обоих типов :) Для конструктора (как и для любого метода) очень важен порядок следования аргументов. Поменяем в нашем конструкторе аргументы имени и возраста местами.

public class Cat {

   String name;
   int age;

   public Cat(int age, String name) {
       this.name = name;
       this.age = age;
   }

   public static void main(String[] args) {

       Cat barsik = new Cat("Барсик", 10); //ошибка!
   }
}
Ошибка! Конструктор четко описывает: при создании объекта Cat ему должны быть переданы число и строка, именно в таком порядке. Поэтому наш код не срабатывает. Обязательно запомни это и учитывай при создании своих собственных классов:

public Cat(String name, int age) {
   this.name = name;
   this.age = age;
}

public Cat(int age, String name) {
   this.age = age;
   this.name = name;
}
Это два абсолютно разных конструктора! Зачем нужен конструктор? - 3Если выразить в одном предложении ответ на вопрос “Зачем нужен конструктор?”, можно сказать: для того, чтобы объекты всегда находились в правильном состоянии. Когда ты используешь конструкторы, все твои переменные будут корректно проинициализированы, и в программе не будет машин со скоростью 0 и прочих “неправильных” объектов. Их использование очень выгодно прежде всего для самого программиста. Если ты будешь инициализировать поля самостоятельно, велик риск что-нибудь пропустить и ошибиться. А с конструктором такого не будет: если ты передал в него не все требуемые аргументы или перепутал их типы, компилятор сразу же выдаст ошибку. Отдельно стоит сказать о том, что внутрь конструктора не стоит помещать логику твоей программы. Для этого в твоем распоряжении есть методы, в которых ты можешь описать весь нужный тебе функционал. Давай посмотрим, почему логика в конструкторе — это плохая идея:

public class CarFactory {

   String name;
   int age;
   int carsCount;

   public CarFactory(String name, int age, int carsCount) {
   this.name = name;
   this.age = age;
   this.carsCount = carsCount;

   System.out.println("Наша автомобильная фабрика называется " + this.name);
   System.out.println("Она была основана " + this.age + " лет назад" );
   System.out.println("За это время на ней было произведено " + this.carsCount +  " автомобилей");
   System.out.println("В среднем она производит " + (this.carsCount/this.age) + " машин в год");
}

   public static void main(String[] args) {

       CarFactory ford = new CarFactory("Ford", 115 , 50000000);
   }
}
У нас есть класс CarFactory, описывающий фабрику по производству автомобилей. Внутри конструктора мы инициализируем все поля, и сюда же помещаем логику: выводим в консоль некоторую информацию о фабрике. Казалось бы — ничего плохого в этом нет, программа прекрасно отработала. Вывод в консоль:
Наша автомобильная фабрика называется Ford Она была основана 115 лет назад За это время на ней было произведено 50000000 автомобилей В среднем она производит 434782 машин в год
Но на самом деле мы заложили мину замедленного действия. И подобный код может очень легко привести к ошибкам. Представим себе, что теперь мы говорим что не о Ford, а о новой фабрике "Amigo Motors", которая существует меньше года и произвела 1000 машин:

public class CarFactory {

   String name;
   int age;
   int carsCount;

   public CarFactory(String name, int age, int carsCount) {
   this.name = name;
   this.age = age;
   this.carsCount = carsCount;

   System.out.println("Наша автомобильная фабрика называется " + this.name);
   System.out.println("Она была основана " + this.age + " лет назад" );
   System.out.println("За это время на ней было произведено " + this.carsCount +  " автомобилей");
   System.out.println("В среднем она производит " + (this.carsCount/this.age) + " машин в год");
}


   public static void main(String[] args) {

       CarFactory ford = new CarFactory("Amigo Motors", 0 , 1000);
   }
}
Вывод в консоль:
Наша автомобильная фабрика называется Amigo Motors Exception in thread "main" java.lang.ArithmeticException: / by zero Она была основана 0 лет назад За это время на ней было произведено 1000 автомобилей at CarFactory.<init>(CarFactory.java:15) at CarFactory.main(CarFactory.java:23) Process finished with exit code 1</init>
Приехали! Программа завершилась с какой-то непонятной ошибкой. Попробуешь догадаться, в чем причина? Причина — в логике, которую мы поместили в конструктор. А конкретно — вот в этой строке:

System.out.println("В среднем она производит " + (this.carsCount/this.age) + " машин в год");
Здесь мы выполняем вычисление и делим количество произведенных машин на возраст фабрики. А поскольку наша фабрика новая (то есть ей 0 лет) — в результате получается деление на 0, которое в математике запрещено. В результате программа завершается с ошибкой. Как же нам стоило поступить? Вынести всю логику в отдельный метод и назвать его, например, printFactoryInfo(). В качестве параметра ему можно передать объект CarFactory. Туда же можно поместить всю логику, и заодно — обработку возможных ошибок, наподобие нашей с нулем лет. Каждому свое. Конструкторы нужны для корректного задания состояния объекта. Для бизнес-логики у нас есть методы. Не стоит смешивать одно с другим.
Комментарии (160)
Чтобы просмотреть все комментарии или оставить свой,
перейдите в полную версию
Mike-L 17 уровень
13 мая 2021
краткое видео где автор объясняет про объекты и конструкторылинк может кому пригодиться И самое интересное (так как ляпы есть и в лекции в курсе и в доп.статьях) Размещайте конструкторы в классе в порядке, зависящем от принимаемых ими числа аргументов: от меньшего к большему неправильно (вернее не комильфо)

class Player {

    Resume(String name, int number) {}
    Resume(String name) {}
    Resume(String name, int number, String uuid) {}
}
комильфо

class Player {

    Resume(String name) {}
    Resume(String name, int number) {}
    Resume(String name, int number, String uuid) {}
}
Oleg 36 уровень, Москва
8 мая 2021
Отличная статья в учебниках. Не так часто встретишь простое нормальное изложение, а тут прямо подача как надо. Автору респект.
Mike-L 17 уровень
5 мая 2021
Для тех у кого возникают недопонимания по конструкторам, вот неплохое видео на примере C++, просто отбросив синтаксис языка линк
Артур Исрапилов 4 уровень, Махачкала
28 марта 2021
Видите ли Oleksii, Вам стоило бы перечитывать по 2, а то и по три раза те места где Вы чего-то поняли. Здесь речь идёт не о классах, а о конструкторе! Конструктор очень похож на метод, за одним исключением, имя конструктора начинается с большой буквы как и имя класса, а у метода имена начинаются с маленькой! На ЭТО стоит обратить внимание!
Oleksii 27 уровень, Харьков
23 марта 2021

С учетом наших требований, конструктор для класса Car будет выглядеть так:

public Car(String model, int maxSpeed) {
   this.model = model;
   this.maxSpeed = maxSpeed;
}

А создание объекта теперь выглядит так:

public static void main(String[] args) {
   Car bugatti = new Car("Bugatti Veyron", 407);
}

Обрати внимание, как создается конструктор.

Он похож на обычный метод, но у него нет типа возвращаемого значения. 
При этом в конструкторе указывается название класса, тоже с большой буквы. 
В нашем случае — Car.
Так до этого название класса всегда указывалось с большой буквы. Или я не понимаю, о чем речь?
FreyGreen 13 уровень, Нижний Новгород
8 марта 2021
Удивительно! Единственная тема которая мне понятна
Александр 15 уровень, шушенское
22 февраля 2021
Хотелось бы добавить к выше сказаному , о чём не пишут , например этот код с пустым конструктором будет валидным

public class SiameseCat
{
	private String eyes;
    public SiameseCat()
    {
	}
	public static void main(String[] args)
	{
		var sianesCat = new SiameseCat();
	}
}

а вот этот код уже не прокатит


public class SiameseCat
{
	private final String eyes;
    public SiameseCat()
    {
	}
	public static void main(String[] args)
	{
		var sianesCat = new SiameseCat();
	}
}

потому что переменную класса объявленную final вы обязаны проинициализировать при обЪявлении или в конструторе , даже если он без параметров

public class SiameseCat
{
	private final String eyes;
    public SiameseCat(){
		this.eyes = "blue";
		System.out.println(" eyes = " + eyes);}
	public static void main(String[] args){
		var sianesCat = new SiameseCat();
	}
}
или так

public class SiameseCat
{
	private final String eyes = "blue";
    public SiameseCat(){
		System.out.println(" eyes = " + eyes);}
	public static void main(String[] args){
		var sianesCat = new SiameseCat();
	}
}
Леонид 7 уровень, Москва
12 февраля 2021
Спасибо, очень полезно!
Мурат 6 уровень, Москва
13 января 2021
Показалось немного непонятным формулировка: "...У него нет никаких аргументов, но он(конструктор по умолчанию) срабатывает каждый раз при создании любого объекта любого класса." Но ниже написали, что если у класса есть конструктор с аргументами, то конструктор по умолчанию пропадает. А значит утверждение выше не совсем верно
Александр Краевский 8 уровень, Днепр
10 января 2021
Хорошая статья, все стало на свои места