Привет! Разберемся, что такое конструктор в Java и зачем он нужен. Тут без преувеличения можно сказать, что этими знаниями ты будешь пользоваться каждый день в реальной работе!
Коротко: конструктор это специальный метод, который срабатывает при создании объекта (new) и задает его начальные значения. Он не дает создать объект без обязательных данных, поэтому объекты всегда оказываются в корректном состоянии.
Ты, возможно, слышишь этот термин впервые, но на самом деле наверняка пользовался конструкторами, только сам не замечал этого :) Мы убедимся в этом позже.
Помнишь, в начале лекции мы говорили, что ты уже пользовался конструкторами, только сам не замечал этого?
Кратко
- Конструктор это специальный метод, который срабатывает при создании объекта и задает его начальные значения.
- У конструктора нет типа возвращаемого значения, а его имя совпадает с именем класса.
- Конструктор не дает создать объект без обязательных аргументов: пропустишь их или перепутаешь типы, получишь ошибку компиляции.
- Конструкторы можно перегружать: несколько вариантов с разным набором параметров.
- Если своих конструкторов нет, работает конструктор по умолчанию; при добавлении конструктора с аргументами он исчезает.
- В конструкторе задают только начальное состояние объекта, а бизнес-логику выносят в отдельные методы.
Что такое конструктор в 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. "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(); //ошибка!
}
}
Видишь, конструктор сделал то, чего мы пытались добиться. Теперь нельзя создать машину без скорости или без модели!
Чем конструктор отличается от обычного метода, удобно свести в таблицу:
| Признак | Конструктор | Обычный метод |
|---|---|---|
| Имя | совпадает с именем класса | любое подходящее |
| Тип возвращаемого значения | отсутствует | есть (или void) |
| Когда вызывается | при создании объекта через new | когда его явно вызвали |
| Перегрузка | да | да |
| Назначение | задать начальное состояние объекта | выполнять действия и логику |
Перегрузка конструкторов
На этом сходство конструкторов и методов не заканчивается. Так же, как и методы, конструкторы можно перегружать. Представь, что у тебя дома живут 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");
}
}
К изначальному конструктору с параметрами "имя" и "возраст" мы добавили еще один, только с именем. Точно так же мы перегружали методы в прошлых уроках.
Теперь мы успешно можем создать оба варианта котов :)
Помнишь, в начале лекции мы говорили, что ты уже пользовался конструкторами, только сам не замечал этого?
Конструктор по умолчанию
Так и есть. Дело в том, что у каждого класса в 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;
}
Это два абсолютно разных конструктора!
Если выразить в одном предложении ответ на вопрос "Зачем нужен конструктор?", можно сказать: для того, чтобы объекты всегда находились в правильном состоянии. Когда ты используешь конструкторы, все твои переменные будут корректно проинициализированы, и в программе не будет машин со скоростью 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
Приехали! Программа завершилась с какой-то непонятной ошибкой. Попробуешь догадаться, в чем причина?
Причина в логике, которую мы поместили в конструктор. А конкретно, вот в этой строке:
System.out.println("В среднем она производит " + (this.carsCount/this.age) + " машин в год");
Здесь мы выполняем вычисление и делим количество произведенных машин на возраст фабрики. А поскольку наша фабрика новая (то есть ей 0 лет), в результате получается деление на 0, которое в математике запрещено.
В результате программа завершается с ошибкой.
Как же нам стоило поступить?
Вынести всю логику в отдельный метод и назвать его, например, printFactoryInfo(). В качестве параметра ему можно передать объект CarFactory. Туда же можно поместить всю логику, и заодно обработку возможных ошибок, наподобие нашей с нулем лет.
Каждому свое. Конструкторы нужны для корректного задания состояния объекта. Для бизнес-логики у нас есть методы. Не стоит смешивать одно с другим.
Официальное описание конструкторов есть в документации Oracle (Java Tutorials).
Вопросы и ответы
Что такое конструктор в Java?
Конструктор это специальный метод, который срабатывает при создании объекта (new) и задает его начальные значения. У него нет типа возвращаемого значения, а имя совпадает с именем класса.Зачем нужен конструктор?
Чтобы объект всегда создавался в корректном состоянии. Конструктор не дает создать объект без обязательных данных: если не передать нужные аргументы или перепутать их типы, компилятор выдаст ошибку.Что такое конструктор по умолчанию?
Это пустой конструктор без аргументов, который Java подставляет автоматически, если в классе нет своих конструкторов. Как только добавляешь конструктор с аргументами, он исчезает, и, если он нужен, его создают явно.Можно ли перегружать конструкторы?
Да. В классе может быть несколько конструкторов с разным набором параметров, например Cat(String name, int age) и Cat(String name). Нужный выбирается по количеству и типам аргументов.Что делает ключевое слово this в конструкторе?
this ссылается на создаваемый объект и помогает отличить поле от аргумента с тем же именем: this.name = name означает "поле name этого объекта = аргументу name".Можно ли писать логику в конструкторе?
Не стоит. Конструктор нужен только для задания начального состояния объекта, а бизнес-логику лучше выносить в отдельные методы. Иначе легко получить ошибку прямо при создании объекта, например деление на ноль.Читайте также
- Знакомство с классами: где живут конструкторы и поля.
- Методы, параметры и перегрузка: конструктор это тоже метод, и его так же перегружают.
- Конструкторы базовых классов: вызов конструктора родителя через super().