Primary constructor
LINEСтатья о том, что такое primary constructor и зачем стоит его использовать.
Класс и объект
В ООП, как ни странно, основным компонентом программы является объект. Мы пишем класс, в котором объявляем все нужные данные для объекта и то, как этот объект будет манипулировать своими данными - методы. Мы выделяем публичный и приватный API для класса с помощью модификаторов доступа, пытаемся не нарушить SRP (принцип единственной ответственности), учесть хорошие практики по проектированию классов. Но все это для того, чтобы в конце концов создать объект, который будет выполнять нужную нам логику.
Для создания объекта существует специальный метод, который обычно не имеет никакого возвращаемого значения и его название совпадает с именем класса. Такой метод называется конструктором, т.к. он конструирует объект.
Для примера напишем класс, описывающий окружность. Класс содержит координату x, y, радиус и значение, заполнена ли окружность цветом.

При создании объекта типа Circle будет выделен участок памяти следующего виде

Размер заголовок каждого объекта зависит от языка и некоторых других особенностей выполнения, компилятора, платформы. Размер каждого типа также зависит от языка. Размер объекта может также быть выровненным по границе машинного слова.
Основная задача конструктора - создать объект в памяти, заполнить поля объекта переданными значения и вернуть объект, готовый к работе.

Если теперь вызвать конструктор объекта, передав ему значения 4 свойств объекта Paint

то конструктор вернем следующий объект

Но что произойдет, если мы передадим в качестве радиуса отрицательное значение? И хотя с точки зрения типов мы ожидаем int и получаем int, смысла в окружности с отрицательным значением немного.

Поэтому конструктор должен также проверить входные параметры на валидность/правильность.

Теперь создать объект с отрицательным радиусом не получится. Нет смысла создавать машину с квадратными колесами, нет смысла создавать самолет без крыльев. Также нет смысла создавать объект с противоречивыми значениями полей.
Резюмируя все выше сказанное - основные задачи конструктора:
- проверить входные данные на валидность
- установить значения, переданные в конструктор, в качестве полей создаваемого объекта
- вернуть созданный объект
Перегрузка конструктора
У класса может быть больше одного конструктора. Иными словами, мы можем по разному инициализировать начальное состояние объекта. В примере выше мы передавали координаты x, y, radius и заполненность цветом. Если подумать, то окружность не имеет смысла без радиуса - по определению окружность это совокупность точек, отстоящей от центра на расстояние, равное радиусу.
Мы можем создать еще один конструктор, который принимает только значение радиуса.

Класс может иметь любое количество конструкторов, но они должны отличаться входными параметрами. Это называется перегрузка конструктора (constructor overloading).
В таком случае мы установим только значение радиуса. Чему будут равны значения других переменных? Они будут иметь значения по умолчанию: для x и y это 0(тип int), а для isFilled это false.
Для конкретного приложения мы можем договориться, что изначальные координаты по x и y - 0 и 0.
Но в таком случае нам опять нужно выполнить проверку радиуса. В итоге класс Circle будет выглядеть так

У нас есть 2 конструктора. Но строчки 9 и 17 повторяются. Всего 2 строчки, это не страшно. Но всего 2 строчки - это просто потому, что пример небольшой. И у нас всего 2 конструктора, их может быть больше.
Еще одна особенность в большинстве ООП языков, это возможность вызова одного конструктора из другого. Точно также, как мы вызываем метод из метода. Для этого используется ключевое слово this. Что если мы выделим из этих конструкторов один и назовем его главный, или primary constructor? (Возможно более подходящий перевод primary - первичный или основной, но в дальнейшем primary constructor будет называться как главный). Скорее всего это должен быть конструктор, который устанавливает все поля, т.е. с 4-мя параметрами. Тогда класс Circle будет выглядеть так

В строке 17 мы вызываем главный конструктор, в который передаем радиус из параметра и значения x, y и isFilled по умолчанию, т.е. 0, 0 и false.
Можно добавить еще один конструктор, который принимает только x, y и radius, подразумевая, что isFIlled = false. Тогда класс Circle будет выглядеть так

В случае Java есть еще одна особенность. Поля, которые помечены как final (а желательно чтобы final, т.е. неизменяемых значений было как можно больше в коде), должны инициализироваться в конструкторе. Но если мы не используем главный конструктор и вызов this(), то должны написать что то вроде такого

Очевидно размер класса увеличивается, а функционал остается таким же. В случае использования главного конструктора все сведется к следующему

Подведем итоги. Primary constructor - прием/подход, когда мы среди всех конструкторов выделяем главный (primary), в котором проверяем все входные параметры, а все остальные конструкторы вызывают этот первичный конструктор. Таким образом проверка входных значений происходит в одном месте.
Несколько конструкторов
Но зачем нужно использовать несколько конструкторов? Почему бы не оставить только первичный, все равно все остальные его используют. Все дело в том, что вызывать вторичный конструктор иногда удобнее, чем приводить параметры к нужному типу. Например, представим класс Point3D, который представляет точку в пространстве.

Некоторые координаты точки в вашей программе приходят в виде строки по сети (предположим). Тогда вам нужно перевести строки к числу, после чего передать эти числа в конструктор. Или обойтись без временных переменных и получить очень длинную строку (строка 7)

Если подобная операция встречается часто, каждое создание будет приводить к дополнительным затратам на перевод типа. Почему бы не вынести эти преобразования в сам класс Point3D, тогда они будут описаны в одном месте а клиентский код (тот, который использует Point3D) станет чище

Теперь создать объект Point3D можно 2-мя способами

Primary constructor в современных языках
В некоторых языках, таких как Scala и Kotlin есть поддержка главного конструктора. Рассмотрим на примере Kotlin.
Главным конструктором считается тот, который идет за названием класса. Вот как класс Point3D будет выглядеть в Kotlin

Если у конструктора нет аннотаций и модификаторов видимости, ключевое слово constructor может быть опущено (из документации).
Kotlin позволяет указывать значения по умолчанию и использовать именованные аргументы, что позволяет зачастую обходиться только главным конструктором.
Также, если у класса есть главный конструктор, то все остальные должны его явно вызывать.
Примеры
В android одним из важнейших классом является View. У класса есть 4 конструктора, среди которых есть конструктор, принимающий context. Все остальные конструкторы вызывают его (не всегда напрямую).

Класс Locale из java.util имеет 3 конструктора, первый из которых главный

Телескопический конструктор
Добавление к главному конструктор других конструкторов нужно для того, чтобы было удобнее создавать объекты. Но если вы пишите класс, у которого более 5 полей и каждое поле может иметь значение, а может и не иметь - то вам понадобится много конструкторов. Один для 1-го и 2-го полей, второй для 2-го, 4-го и 5-го, третий для 1-го и 5-го и т.д.
Для такого случая был придуман отдельный термин - телескопический конструктор (telescoping constructor). Если вы заметили такую ситуацию - вы идете не туда. Добавить несколько конструкторов для удобства - неплохая идея, но добавить десяток на все возможные и невозможные случаи жизни - однозначно плохая идея. Класс становится слишком большим и трудно поддерживаемым. Вам становится сложно выбрать подходящий конструктор из имеющихся. Решением может стать или пересмотр API класса, или использование билдера (builder pattern).
Заключение
Primary constructor - подход при проектировании API класса, когда вы выделяете один главный, первичный конструктор для создания объекта, а все остальные конструкторы вызывают его, предварительно преобразуя данные к нужному типу.
При использовании такого подхода вся проверка входных значений остается в одном месте - в главном конструкторе, а пользователи класса имеют несколько способов создания объекта.
Некоторые языки, например Kotlin поддерживают эту идею на уровне языка.
Если количество конструкторов больше 3 - 5 (точного числа никто не скажет) и каждый отличается одним значением, скорее всего лучше пересмотреть API класса.