Привет! Сегодня мы разберем очень важную тему, которая касается наших объектов. Тут без преувеличения можно сказать, что этими знаниями ты будешь пользоваться каждый день в реальной работе!
Мы поговорим о конструкторах.
Ты, возможно, слышишь этот термин впервые, но на самом деле наверняка пользовался конструкторами, только сам не замечал этого :) Мы убедимся в этом позже.
Помнишь, в начале лекции мы говорили, что ты уже пользовался конструкторами, только сам не замечал этого?
Так и есть. Дело в том, что у каждого класса в Java есть так называемый конструктор по умолчанию.
У него нет никаких аргументов, но он срабатывает каждый раз при создании любого объекта любого класса.
Что такое конструктор в 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");
}
}
К изначальному конструктору с параметрами “имя” и “возраст” мы добавили еще один, только с именем. Точно так же мы перегружали методы в прошлых уроках.
Теперь мы успешно можем создать оба варианта котов :)

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;
}
Это два абсолютно разных конструктора!
Если выразить в одном предложении ответ на вопрос “Зачем нужен конструктор?”, можно сказать: для того, чтобы объекты всегда находились в правильном состоянии. Когда ты используешь конструкторы, все твои переменные будут корректно проинициализированы, и в программе не будет машин со скоростью 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. Туда же можно поместить всю логику, и заодно — обработку возможных ошибок, наподобие нашей с нулем лет.
Каждому свое. Конструкторы нужны для корректного задания состояния объекта. Для бизнес-логики у нас есть методы. Не стоит смешивать одно с другим.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ