Помню, как на втором месяце изучения Java я писал класс для геометрических фигур. У меня было три конструктора для класса Circle, и в каждом я копировал одни и те же строчки инициализации. Код превратился в кашу из повторений, и я подумал: "Должен же быть способ избежать этого дублирования!" Спойлер: способ есть, и называется он this(). Но как часто бывает с новичками, я сначала сделал всё неправильно. И компилятор мне за это "спасибо" не сказал. Давайте разберёмся, как вызывать один конструктор из другого правильно, и главное — какие ошибки не стоит повторять.Вызов конструктора из конструктора - 1

Зачем вообще вызывать конструктор из конструктора?

Представьте: у вас есть класс с несколькими конструкторами, которые инициализируют разное количество полей. Часть кода у них общая — например, первые два поля всегда инициализируются одинаково. Вместо того чтобы копировать этот код в каждый конструктор, можно один раз написать базовую инициализацию, а потом просто переиспользовать её. Это не только экономит время, но и делает код более поддерживаемым. Если нужно что-то изменить в логике инициализации, вы меняете в одном месте, а не бегаете по всем конструкторам.

Как это делается правильно

Для вызова одного конструктора из другого используется ключевое слово this, после которого в круглых скобках указываются параметры для соответствующего конструктора. Вот простой пример:
public class SomeClass {
    int a;
    int b;
    int c;

    public SomeClass(int a, int b) {
        this.a = a;
        this.b = b;
    }

    public SomeClass(int a, int b, int c) {
        // Вызов конструктора с двумя параметрами
        this(a, b);
        this.c = c;
    }

    public void show() {
        System.out.println("a = " + a);
        System.out.println("b = " + b);
        System.out.println("c = " + c);
    }
}
В конструкторе с тремя параметрами мы вызываем конструктор с двумя параметрами через this(a, b), а затем инициализируем только третью переменную. Получается, что код инициализации a и b написан один раз, а используется в обоих конструкторах.

Более реалистичный пример

В учебных примерах конструкторы обычно простые — пара присваиваний и всё. Но в реальной жизни конструкторы могут быть гораздо сложнее. Вот условный пример класса, который умеет отправлять GET и POST запросы:
public class Request {
    String url, postData, headers, method;
    boolean auth;
    Connection connection;

    public Request(String url, boolean auth) {
        this.method = "GET";
        this.url = url;
        if (auth) {
            this.headers = this.getAuthHeaders(Application.config);
        }
        this.connection = new Connection(Application.config);
        // и это очень условный пример, в реальной жизни всё ещё сложнее
    }

    public Request(String url, boolean auth, String postData) {
        this(url, auth);  // Переиспользуем всю логику из первого конструктора
        this.method = "POST";
        this.postData = postData;
    }
}
Видите? Вместо того чтобы копировать всю логику настройки соединения, заголовков и прочего, мы просто вызываем первый конструктор и добавляем только то, что специфично для POST-запроса.

Типичная ошибка новичка (я тоже так делал)

А теперь самое интересное. Когда я впервые узнал про this(), я подумал: "Окей, я могу сначала инициализировать пару переменных, а потом вызвать конструктор для оставшихся!" И написал вот такой код:
public class Circle {
    public double x;
    public double y;
    public double radius;

    public Circle(double x, double y, double radius) {
        this.x = x;
        this.y = y;
        this.radius = radius;
    }

    public Circle(double x, double y) {
        this.x = x;         // Сначала инициализируем
        this.y = y;         // две переменные
        this(x, y, 10);     // А потом вызываем конструктор
    }

    public Circle() {
        this(5, 5, 1);
    }
}
Логика мне казалась железной: сначала инициализируем x и y, потом вызываем конструктор, который присвоит им те же значения ещё раз (ну и что?) плюс инициализирует radius. Какие проблемы? Компилятор мне объяснил проблемы очень быстро. Код не скомпилировался.

Почему так нельзя: техническое объяснение

Вызов другого конструктора (через this() или super()) должен быть либо первой строкой в конструкторе, либо его вообще не должно быть. Это жёсткое правило Java, и компилятор его не нарушит. Вот что происходит под капотом. Когда вы пишете конструктор и не вызываете явно другой конструктор, компилятор автоматически добавляет вызов конструктора родительского класса — super(). Причём добавляет его первой строкой. Когда я написал:
public Circle(double x, double y) {
    this.x = x;
    this.y = y;
    this(x, y, 10);
}
Компилятор видит это так:
public Circle(double x, double y) {
    super();            // Компилятор добавил это неявно
    this.x = x;
    this.y = y;
    this(x, y, 10);     // Ошибка! Попытка вызвать конструктор после super()
}
Получается, что объект уже был создан (через super()), а я пытаюсь создать его заново через this(). Это и есть причина ошибки. Конструктор — это не просто инициализатор переменных. Он отвечает за создание самого объекта. А создавать заново уже созданный объект нельзя. Ещё одна важная деталь: все эти переменные (x, y, radius) — это не "глобальные переменные". Это поля экземпляра класса. Зачем их инициализировать, если потом вы вызовете другой конструктор, который присвоит им другие (или те же) значения?

Золотые правила вызова конструкторов

Давайте сформулируем чёткие правила, чтобы не наступать на те же грабли: 1. Вызов другого конструктора (this() или super()) должен быть первой строкой кода в вызывающем конструкторе. Не второй, не третьей — именно первой. 2. В одном конструкторе может быть только один вызов другого конструктора. Либо вы вызываете конструктор своего класса через this(), либо конструктор родительского класса через super(), либо не вызываете ничего (тогда компилятор сам добавит super()). 3. Конструкторы Java исключают рекурсию. Нельзя написать конструктор, который вызывает сам себя (прямая рекурсия), или цепочку конструкторов, которая замыкается в кольцо (косвенная рекурсия). Компилятор такой код просто не пропустит. Примеры неправильной рекурсии:
// Прямая рекурсия — не скомпилируется
class A {
    public A(int x) {
        this(1);
    }
}

// Косвенная рекурсия — тоже не скомпилируется
class A {
    public A() {
        this(1);
    }
    public A(int x) {
        this();
    }
}
4. Вложенный конструктор не может иметь никакой явной или неявной ссылки на this в качестве параметра. То есть вы не можете передать результат вызова нестатического метода или внутренний класс в качестве параметра конструктора при вызове через this(). Почему? Потому что объект ещё не создан! Вы находитесь в конструкторе, а это значит, что объект, на который ссылается this, ещё не существует полностью.

Можно ли вызывать this() в обычных методах?

Нет, this() используется только в конструкторах. Если вам нужно переиспользовать код в обычных методах, используйте обычный вызов метода. Вот пример:
public class Car {
    private int speed;
    private int price;
    private String model;

    public void setInfo(int speed, int price) {
        this.speed = speed;
        this.price = price;
    }

    public void setInfo(int speed, int price, String model) {
        setInfo(speed, price);  // Просто вызываем первый метод
        this.model = model;
    }
}
Кстати, в методах this необязателен — компилятор и так поймёт, что вызывается метод текущего объекта. В конструкторах же this() — это специальный синтаксис для вызова другого конструктора.

Частые вопросы

При вызове конструктора Java автоматом определяет, какой вызывать?
Да, Java использует механизм перегрузки. В зависимости от количества и типов параметров компилятор определяет, какой именно конструктор нужно вызвать. Почему нельзя написать this.SomeClass(a, b)?
Потому что конструктор — это не метод объекта. У него нет имени в обычном смысле. Для вызова конструктора используется специальный синтаксис this() или super(). Можно ли вызвать конструктор с меньшим количеством параметров из конструктора с бóльшим?
Да, это стандартная практика. Обычно делают наоборот: более простой конструктор вызывает более сложный, подставляя значения по умолчанию для недостающих параметров. Сколько раз можно вызывать this() в одном конструкторе?
Только один раз, и обязательно первой строкой. Если вам нужна сложная цепочка вызовов, она строится через несколько конструкторов: первый вызывает второй, второй вызывает третий и т.д. Что если мне нужно выполнить какую-то логику ДО вызова другого конструктора?
Никак. Вызов должен быть первой строкой. Если вам нужна предварительная обработка параметров, вынесите её в статический метод и вызывайте его прямо в параметрах: this(prepareValue(param), otherParam).

Вывод

Вызов одного конструктора из другого через this() — это мощный инструмент для избежания дублирования кода. Главное — помнить о золотом правиле: this() должен быть первой строкой в конструкторе. Не пытайтесь инициализировать часть переменных, а потом вызвать конструктор. Не пытайтесь создать рекурсивные конструкторы. И помните, что конструктор — это не просто инициализатор переменных, а механизм создания объекта. Чем раньше вы привыкнете к этим правилам, тем меньше времени потратите на борьбу с ошибками компиляции. А освободившееся время сможете потратить на изучение чего-то более интересного — например, паттернов проектирования или многопоточности. Практикуйтесь, экспериментируйте и не бойтесь ошибок. Каждая ошибка компиляции — это маленький урок от компилятора. Главное — понять, что он пытается вам сказать.