Конструкторы и this
В прошлом уроке точку собирали в три строки: new Point(), потом p.x = 3; и p.y = 4;. Если забыть одну из них, в поле останется 0, и компилятор промолчит. А десять точек потребуют тридцати строк.
Вернёмся к чертежу. Дом по нему строит бригада: она ставит стены, вставляет окна и только потом отдаёт ключи. Новосёлы получают готовый дом и ничего не достраивают сами. В классе роль бригады играет конструктор — код, который выполняется при создании каждого объекта:
public class Main {
public static void main(String[] args) {
Point a = new Point(3, 4);
System.out.println(a.x + " " + a.y); // 3 4
}
}
class Point {
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}Давайте разберём программу по шагам:
Point(int x, int y)внутри класса — конструктор. Его имя совпадает с именем класса.new Point(3, 4)строит объект и сразу вызывает конструктор с аргументами3и4.this.x = x;иthis.y = y;кладут эти числа в поля нового объекта.- Ссылка на готовую точку попадает в
a, иprintlnпечатает3 4.
Что такое конструктор
Конструктор похож на метод, но у него свои правила:
- имя совпадает с именем класса, вплоть до заглавной буквы:
Point; - типа результата нет, даже
void: сvoidэто был бы обычный метод по имениPoint; - его вызывает
new, ровно один раз для каждого объекта; - параметры работают как у метода: аргументы из
new Point(3, 4)попадают в них по порядку.
В конструкторе можно писать любой код: проверить аргументы, напечатать строку. Но главная его работа — заполнить поля нового объекта.
this.x и x
Внутри конструктора есть два разных x. Первый — параметр: он появляется при вызове и исчезает, когда конструктор закончится. Второй — поле объекта, которое останется с точкой. Имя без уточнения означает ближайшую переменную, то есть параметр: он закрывает собой поле.
Чтобы добраться до поля, пишут this.x. this — ссылка на объект, который сейчас строится. Каждый new строит свой объект, поэтому и this у каждого вызова свой. Получается так:
this.x— полеxнового объекта;x— параметр, то есть число из вызоваnew Point(3, 4).
В виджете программа строит две точки. Рамка Point(…) — это работающий конструктор: в ней видны параметры x и y и поля нового объекта this.x и this.y. Нажимайте «Следующий шаг» и следите, куда попадает каждое число.
1public class Main {2 public static void main(String[] args) {3 Point a = new Point(3, 4);4 Point b = new Point(5, 6);5 System.out.println(a.x + " " + a.y);6 System.out.println(b.x + " " + b.y);7 }8}9 10class Point {11 int x;12 int y;13 Point(int x, int y) {14 this.x = x;15 this.y = y;16 }17}
new Point(3, 4) строит объект с полями x и y, равными 0, и вызывает конструктор.
Когда конструктор заканчивается, this и параметры исчезают вместе с его рамкой, а объект остаётся, и ссылка на него попадает в a или b. Строка this.x = x во втором вызове не трогает первую точку: у второго вызова свой this.
Здесь картинка с бригадой неточна. Настоящая бригада начинает с пустого участка, а конструктор получает уже построенный объект. JVM создаёт его с полями по умолчанию, 0 у x и y, и только потом запускает конструктор.
Конструктор по умолчанию
Как же тогда работал new Point() в классе, где конструктора не было вовсе? Если в классе нет ни одного конструктора, Java сама добавляет пустой, без параметров. Это конструктор по умолчанию. Но стоит написать свой, и Java его больше не добавляет:
Point a = new Point(3, 4);
Point b = new Point(); // ✗error: constructor Point in class Point cannot be applied to given types;Программа не компилируется. В классе остался один конструктор, и он ждёт два int. Java не подставляет недостающие аргументы и не ищет пустой конструктор. Вызов должен подойти к одному из написанных конструкторов по числу и типам аргументов. Это проверяет компилятор, ещё до запуска.
Этот вопрос любят задавать на собеседовании: когда у класса есть конструктор по умолчанию? Только когда вы не написали ни одного своего.
Вызов this(…)
Чтобы new Point() снова работал, конструктор без параметров пишут сами. Конструкторов в классе может быть несколько, как у перегруженного метода, — лишь бы различались их параметры:
class Point {
int x;
int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
Point() {
this(0, 0);
}
}Запись this(0, 0) вызывает другой конструктор того же класса — тот, что принимает два int. Поля заполняет один конструктор, а остальные передают работу ему. Если позже в основной конструктор добавится проверка, она сработает при любом способе создать точку.
Вызов this(…) пишут первой строкой конструктора. Java 21 другого порядка не допускает, а Java 25 разрешает перед ним некоторые инструкции, например проверку аргументов. В курсе код пишется так, чтобы он компилировался и на Java 21.
Частая ошибка
Забыли this перед одним из полей:
class Point {
int x;
int y;
Point(int x, int y) {
this.x = x;
y = y; // ✗
}
}Программа компилируется, а new Point(3, 4) даёт точку 3 0. Строка y = y кладёт параметр y в него же, а поле y так и остаётся 0. Компилятор об этом не предупреждает. Пишите this. перед каждым полем, у которого есть параметр с тем же именем: this.y = y;.
Главное
- Конструктор называется как класс и не имеет типа результата;
newвызывает его для каждого нового объекта. - Внутри конструктора
this.x— поле нового объекта, аx— параметр;y = yбезthisполе не меняет. - Java добавляет конструктор по умолчанию, только если в классе нет ни одного своего конструктора.
this(…)первой строкой конструктора вызывает другой конструктор того же класса.