kotlin параметры по умолчанию
Функциональное программирование
Функции и их параметры
Например, определим и вызовем функцию, которая просто выводит некоторую строку на консоль:
Функции можно определять в файле вне других функций или классов, сами по себе, как например, определяется функция main. Такие функции еще называют функциями верхнего уровня (top-level functions).
Здесь кроме главной функции main также определена функция hello, которая не принимает никаких параметров и ничего не возвращает. Она просто выводит строку на консоль.
Функция hello (и любая другая определенная функция, кроме main) сама по себе не выполняется. Чтобы ее выполнить, ее надо вызвать. Для вызова функции указывается ее имя (в данном случае «hello»), после которого идут пустые скобки.
Таким образом, если необходимо в разных частях программы выполнить одни и те же действия, то можно эти действия вынести в функцию, и затем вызывать эту функцию.
Предача параметров
Консольный вывод программы:
Аргументы по умолчанию
В примере выше при вызове функций showMessage и displayUser мы обязательно должны предоставить для каждого их параметра какое-то определенное значение, которое соответствует типу параметра. Мы не можем, к примеру, вызвать функцию displayUser, не передав ей аргументы для параметров, это будет ошибка:
Однако мы можем определить какие-то параметры функции как необязательные и установить для них значения по умолчанию:
Консольный вывод программы
Именованные аргументы
При вызове функции в скобках мы можем указать название параметра и с помощью знака равно передать ему нужное значение.
При этом, как видно из последнего случае, необязательно все аргументы передавать по имени. Часть аргументов могут передаваться параметрам по позиции. Но если какой-то аргумент передан по имени, то остальные аргументы после него также должны передаваться по имени соответствующих параметров.
Также если до обязательного параметра функции идут необязательные параметры, то для обязательного параметра значение передается по имени:
Изменение параметров
По умолчанию все параметры функции равносильны val-переменным, поэтому их значение нельзя изменить. Например, в случае следующей функции при компиляции мы получим ошибку:
Однако если параметр предствляет какой-то сложный объект, то можно изменять отдельные значения в этом объекте. Например, возьмем функцию, которая в качестве параметра принимает массив:
Здесь функция double принимает числовой массив и увеличивает значение его первого элемента в два раза. Причем изменение элемента массива внутри функции приведет к тому, что также будет изменено значение элемента в том массиве, который передается в качестве аргумента в функцию, так как этот один и тот же массив. Консольный вывод:
Функции
Объявление функций
В Kotlin функции объявляются с помощью ключевого слова fun
Применение функций
При вызове функции используется традиционный подход
Для вызова вложенной функции используется знак точки
Инфиксная запись
Функции так же могут быть вызваны при помощи инфиксной записи, при условии, что:
Параметры
Параметры функции записываются аналогично системе обозначений в языке Pascal, имя:тип. Параметры разделены запятыми. Каждый параметр должен быть явно указан.
Аргументы по умолчанию
Параметры функции могут иметь значения по умолчанию, которые используются в случае, если аргумент функции не указан при её вызове. Это позволяет снизить уровень перегруженности кода по сравнению с другими языками.
Значения по умолчанию указываются после типа знаком =.
Переопределённые методы всегда используют те же самые значения по умолчанию, что и их базовые методы. При переопределении методов со значениями по умолчанию эти параметры должны быть опущены:
Если параметр по умолчанию предшествует параметру без значения по умолчанию, значение по умолчанию можно использовать только при вызове функции с именованными аргументами:
Но если последний аргумент lambda передается в вызов функции вне скобок, передача значений параметров по умолчанию не допускается:
Именованные аргументы
Имена параметров могут быть явно указаны при вызове функций. Это очень удобно, когда у функции большой список параметров, в том числе со значениями по умолчанию.
Рассмотрим следующую функцию:
мы можем вызвать её, используя аргументы по умолчанию
Однако, при вызове этой функции без аргументов по умолчанию, получится что-то вроде
С помощью именованных аргументов мы можем сделать код более читабельным:
Или, если нам не нужны все эти аргументы
Переменное число аргументов (vararg) можно передать в именованной форме с помощью оператора spread:
Обратите внимание, что синтаксис именованных аргументов не может быть использован при вызове Java функций, потому как байт-код Java не всегда сохраняет имена параметров функции.
Функции с возвращаемым типом Unit
Указание типа Unit в качестве возвращаемого значения тоже не является обязательным. Код, написанный выше, совершенно идентичен с
Функции с одним выражением
Когда функция возвращает одно-единственное выражение, фигурные скобки < >могут быть опущены, и тело функции может быть описано после знака =
Компилятор способен сам определить типа возвращаемого значения.
Явные типы возвращаемых значений
Функции, в которых есть тело, всегда должны указывать возвращаемый ими тип данных (если в этом качестве не указан тип Unit ). Kotlin не вычисляет самостоятельно тип возвращаемого значения для функций с заключённым в них блоком кода потому, что подобные функции могут иметь сложную структуру и возвращаемый тип неочевиден для читающего этот код человека (иногда даже для компилятора).
Нефиксированное число аргументов (Varargs)
Параметр функции (обычно для этого используется последний) может быть помечен модификатором vararg :
это позволит указать множество значений в качестве аргументов функции:
Область действия функций
В Kotlin функции могут быть объявлены в самом начале файла. Подразумевается, что вам не обязательно создавать объект какого-либо класса, чтобы воспользоваться его функцией (как в Java, C# или Scala). В дополнение к этому, функции в языке Kotlin могут быть объявлены локально, как функции-члены (ориг. «member functions») и функции-расширения («extension functions»).
Локальные функции
Kotlin поддерживает локальные функции. Например, функции, вложенные в другие функции
Такие локальные функции могут иметь доступ к локальным переменным внешних по отношению к ним функций (типа closure). Таким образом, в примере, приведённом выше, visited может быть локальной переменной.
Функции-элементы
Функции-элементы вызываются с использованием точки
Для более подробной информации о классах и их элементах см. Классы
Функции-обобщения (Generic Functions)
Функции могут иметь обобщённые параметры, которые задаются треугольными скобками и помещаются перед именем функции
Для более подробной информации об обобщениях см. Обобщения
Встроенные функции (Inline Functions)
О встроенных функциях рассказано здесь
Функции-расширения (Extension Functions)
О расширениях подробно написано в отдельной статье
Высокоуровневые функции и лямбды
О лямбдах и высокоуровневых функциях см. раздел Лямбды
Функции с хвостовой рекурсией
Kotlin поддерживает такой стиль функционального программирования, более известный как «хвостовая рекурсия». Это позволяет использовать циклические алгоритмы вместо рекурсивных функции, но без риска переполнения стэка. Когда функция помечена модификатором tailrec и её форма отвечает требованиям компилятора, он оптимизирует рекурсию, оставляя вместо неё быстрое и эффективное решение этой задачи, основанное на циклах.
Этот код высчитывает fixpoint косинуса, который является математической константой. Он просто напросто постоянно вызывает Math.cos, начиная с 1.0 до тех пор, пока результат не изменится, приняв значение 0.7390851332151607. Получившийся код эквивалентен вот этому более традиционному стилю:
10 главных конструкций языка Kotlin
Почти как Java, но есть отличия.
Чтобы быстро начать программировать на языке, достаточно знать несколько основных конструкций и общих правил написания кода. Со временем вы освоите все нюансы, а на старте можно использовать что-то попроще.
Если вы не знаете, зачем нужен Kotlin и что на нём программируют, почитайте нашу статью про Kotlin. А если знаете — вот вам краткий справочник по основам языка.
👉 В Kotlin будет много конструкций, похожих на Java. Это нормально, потому что Kotlin работает тоже поверх виртуальной машины JVM, как и Java.
Точка с запятой в Котлине не нужна.
Комментарии
Всё как обычно — есть однострочные и многострочные комментарии:
// Это однострочный комментарий
// Для каждой строки нужно добавлять его отдельно
/* А это — многострочный
его можно сделать любой длины,
если в начале и в конце поставить нужные символы */
Многие программисты ругают Kotlin за то, что ключевые слова для переменной и константы отличаются всего на одну букву:
val — объявляет константу
var — объявляет переменную
Из-за этого легко ошибиться и сделать константу вместо переменной и наоборот.
Ввод и вывод
Чтобы ввести что-то с клавиатуры, нам нужна переменная, где будет храниться результат ввода и команда readLine(). Эта команда ждёт, когда пользователь введёт любые данные и нажмёт Enter. После этого всё, что ввёл пользователь, отправляется в ту переменную.
Для вывода используются команды print() и println() — первая оставляет курсор на той же строке, а вторая после вывода переводит курсор на новую строку.
Чтобы вывести значение переменной в команде вывода, перед переменной ставят знак доллара:
Присваивание и сравнение
Всё так же, как в Java, но без точек с запятой:
// это присваивание
x = 10
// а это — сравнение x и 10
// результат сравнения отправляется в переменную b
var bol: Boolean
b = (x == 10)
Условный оператор if
Работает как обычно: сначала проверяется условие, и если оно истинно — выполняется код, который идёт после условия. Если результат проверки ложный — выполняется код, написанный после else, или не выполняется ничего, если такого блока в условии нет. Условие пишется в скобках, фигурные скобки можно не писать, если нужно выполнить только одну команду.
Оператор множественного выбора when
Аналог классического Case или Switch из других языков программирования. Команда смотрит на значение переменной, которое к ней поступило, и сравнивает это значение со своим списком вариантов. Если есть совпадение — выполняется нужная команда.
Циклы
Проще всего работать с циклом for: достаточно указать имя переменной цикла и диапазон, в котором она будет меняться:
При желании можно указать шаг цикла внутри диапазона (по умолчанию он равен единице):
for (i in 1..6 step 2) <
print(«$i «) // на экране появятся числа 1, 3 и 5
>
Циклы с предусловием и с постусловием выглядят и работают как в обычном Java:
Функции
У функций в Kotlin полная свобода:
👉 А ещё в Kotlin, как и в Java, каждая программа состоит из функций и каждая команда должна быть внутри какой-то функции. Основная функция программы называется main.
Самое простое объявление и использование функций в Kotlin выглядит так:
Классы
Классы в Котлине поддерживают полноценные конструкторы и инициализации, но для старта достаточно объявить имя класса и его содержимое:
Объекты
Как и в большинстве ООП-языков, для создания нового объекта достаточно указать класс, от которого произойдёт объект. После этого с объектом можно работать по всем правилам объектно ориентированного программирования:
Kotlin параметры по умолчанию
Если статья вам понравилась, то можете поддержать проект.
Коты забавные, поэтому ввели ключевое слово fun (есть спорное мнение, что на самом деле это сокращение от «function» для обозначения функций, которые являются аналогами методов в Java).
Объявление функции начинается с ключевого слова fun, затем идёт имя функции, в круглых скобках указываются параметры. Тип возвращаемого значения указывается после списка параметров и отделяется от него двоеточием. Функция всегда возвращает значение. Если вы сами не указали возвращаемое значение, то функция вернёт Unit, который схож с void, но является объектом.
Стандартный вывод «Hello Kitty» для Kotlin-программы (Desktop, не Android):
Данная функция ничего не возвращает. Напишем другую функцию, возвращающую результат.
Обратите внимание, что if является выражением в Kotlin, а не Java-оператором и соответствует тернарному оператору в Java:
Простую функцию, в которой блок состоит из одной строки кода, можно переписать в одну строчку.
Можно даже убрать возвращаемый тип. Гулять так гулять.
Такой способ подходит только для функций, в которых Kotlin способен самостоятельно разобраться, чего хотел разработчик, т.е. с телом-выражением в правой части. В правой части мы вычисляем какой-то результат, который обычно передавали в return. Теперь мы можем отказаться от return и фигурных скобок, и сразу присваивать результат функции.
В других случаях (тело-блок) вы обязаны указывать возвращаемый тип и использовать инструкцию return.
Функции верхнего уровня можно импортировать для сокращения кода.
Доступен вариант со звёздочкой.
Можно даже изменить имя и создать псевдоним при помощи ключевого слова as. Этот вариант может оказаться полезным, если имеются несколько одинаковых названий функций из разных пакетов и хочется избежать путаницы и конфликтов.
Именованные параметры
Мы привыкли, что при вызове метода следует соблюдать очерёдность параметров. С именованными параметрами такая необходимость отпала. Создадим новую функцию из двух параметров.
Вызывая функцию, мы можем не соблюдать порядок параметров, если явно будем прописывать имена параметров.
Несмотря на то, что мы поменяли местами параметры, итоговый результат всё равно будет работать правильно. Такой подход может пригодиться, когда вы не помните порядок и вам лень смотреть документацию.
Данный приём не сработает при работе с методами, написанными на Java. Поддержка именованных аргументов есть в Java 8, но Kotlin поддерживает совместимость с Java 6, поэтому приходится смириться. Возможно, в будущем, эта проблема решится автоматически, когда откажутся от поддержки старых версий.
Параметры по умолчанию
Добавим в класс активности новую функцию для вывода всплывающего сообщения (в примере используется функция-расширение).
Второй параметр использует значение по умолчанию и мы можем его не указывать при вызове. Вызываем функцию.
С параметрами по умолчанию нужно быть внимательными, возможна ситуация, когда Kotlin не поймёт, что вы от него хотите. Создадим функцию из трёх параметров, один из них будет иметь значение по умолчанию.
Вызываем функцию с двумя параметрами, надеясь, что третий подставится самостоятельно. Но Kotlin не может решить, какой параметр пропущен.
В этом случае на помощь приходят именованные параметры.
Третий параметр теперь нам известен, опущенный параметр относится ко второму, оставшийся относится к первому.
У класса Thread имеется восемь конструкторов! Вы можете создавать гораздо удобные решения с параметрами по умолчанию.
Unit. Если функция ничего не возвращает
Стоит немного рассказать о функциях, которые не возвращают никаких значений. В Java мы используем ключевое слово void для подобных случаев. В Kotlin был придуман новый тип Unit для подобных ситуаций. Получается, что функция всегда что-то возвращает, в нашем случае Unit, который мы никак не используем.
Но Kotlin достаточно умен и понимает, что мы не хотим ничего возвращать. Поэтому мы можем сократить код.
Можно сократить код, убрав фигурные скобки, так как у функции только одно выражение.
Вызываем функцию с любым количеством аргументов.
Если функция имеет и другие параметры, то они должны быть раньше vararg. Можно обойти это правило, если использовать именованные параметры, но лучше избегать таких ситуаций.
По сути vararg работает с массивом, но простое добавление массива Kotlin не пропустит. Следует использовать специальный оператор *.
Вложенные (локальные) функции
Внутри одной функции можно создать ещё одну локальную функцию.
Вложенная функция имеет доступ к переменным своей родительской функции.
Функции верхнего уровня
Функцию можно объявить в начале файла, не обязательно размещать его в теле класса. Это удобно, когда вам нужны методы, которые не относятся к конкретному классу или вы не хотите перезагружать имеющийся класс лишним кодом. Часто для этих целей программисты создавали отдельные классы со словом Util.
Размещая код метода за пределами класса, вы избегаете лишней вложенности. Они по-прежнему являются членами пакета, прописанного в файле и могут импортироваться при использовании в других пакетах.
Таким образом можно создать новый файл без всяких классов, указав только пакет.
Kotlin незаметно для вас создаст класс CatsKt по имени файла и все функции скомпилирует в статические методы. Если будете вызывать функцию в Java-коде, то это будет выглядеть следующим образом.
Если имя класса вас не устраивает, то добавьте аннотацию @JvmName перед именем пакета.
Тогда вызов в Java-коде будет другим.
Функция TODO()
В стандартную библиотеку Kotlin входит функция TODO() (надо сделать). Её описание выглядит следующим образом.
Функция TODO() возбуждает исключение, т.е. вызов функции гарантированно завершится ошибкой — она возвращает тип Nothing. Считайте функцию временной заглушкой. Разработчик знает, что некоторая функция должна вернуть строку или другой объект, но пока отсутствуют другие функции, необходимые для ее реализации. Создадим для примера две функции.
Обратите внимание, что возвращаемое значение для shouldReturnAString() — это String, но на самом деле функция ничего не возвращает. Аналогично у shouldReturnACat().
Возвращаемый тип Nothing у TODO() показывает компилятору, что функция гарантированно вызовет ошибку, поэтому проверять возвращаемое значение после TODO() не имеет смысла, так как shouldReturnAString() и shouldReturnACat() ничего не вернут. Компилятор не будет ругаться, а разработчик может продолжать разработку, отложив на потом реализацию функции-заглушки.
Функцию можно вызвать без аргументов. Код, который будет следовать за функцией, будет недостижим.
Имена функций в обратных кавычках
Можно объявить или вызвать функцию с именем, содержащим нестандартные символы. Для этого достаточно заключить имя в обратные кавычки `. Например, объявим функцию:
Данная возможность нужна, чтобы поддерживать совместимость с Java в тех моментах, когда встречаются зарезервированные ключевые слова. Использование обратных кавычек позволяют избежать несовместимости в случаях, если это необходимо. На практике такое почти не встречается.
На данный момент под Android такой способ не работает, студия будет ругаться.
Kotlin: копаем глубже. Конструкторы и инициализаторы
В уже далёком мае 2017 года Google объявила о том, что Kotlin стал официальным языком для разработки под Android. Кто-то тогда впервые услышал название этого языка, кто-то на нём уже продолжительное время писал, но с того момента стало понятно, что все, кто близок к Android-разработке, теперь просто обязаны познакомиться с ним. Далее последовали как восторженные отклики «Наконец-то!», так и жуткое негодование «Зачем нам нам новый язык? Чем Java не угодила?» и т.д. и т.п.
С тех пор прошло достаточно времени, и хоть споры о том, хороший Kotlin или плохой, до сих пор не утихли, всё больше кода под Android пишется именно на нём. И даже вполне консервативные разработчики тоже переходят на него. Кроме того, в сети можно наткнуться на информацию, что скорость разработки после освоения этого языка увеличивается на 30% по сравнению с Java.
Сегодня Kotlin уже успел вылечиться от нескольких детских болезней, оброс большим количеством вопросов и ответов на Stack Overflow. Невооружённым взглядом стали видны как его плюсы, так и слабые места.
И вот на этой волне мне пришла в голову идея подробно разобрать отдельные элементы молодого, но популярного языка. Обратить внимание на сложные моменты и сравнить их с Java для наглядности и лучшего понимания. Разобраться в вопросе несколько глубже, чем это можно сделать, прочитав документацию. Если эта статья вызовет интерес, то, скорее всего, она положит начало целому циклу статей. А пока начну с довольно базовых вещей, которые, тем не менее, скрывают массу подводных камней. Поговорим о конструкторах и инициализаторах в Kotlin.
Как и в Java, в Kotlin создание новых объектов 一 сущностей определённого типа 一 происходит при помощи вызова конструктора класса. В конструктор также можно передавать аргументы, а конструкторов может быть несколько. Если смотреть на этот процесс как бы снаружи, то здесь единственное отличие от Java — отсутствие ключевого слова new при вызове конструктора. Теперь заглянем глубже и разберёмся, что же происходит внутри класса.
У класса могут быть первичный (primary) и дополнительные (secondary) конструкторы.
Конструктор объявляется с помощью ключевого слова constructor. В случае если у первичного конструктора нет модификаторов доступа и аннотаций, ключевое слово можно опустить.
У класса может не быть конструкторов, объявленных явно. В этом случае после объявления класса нет никаких конструкций, мы сразу переходим к телу класса. Если проводить аналогию с Java, то это равнозначно отсутствию явного объявления конструкторов, в результате чего конструктор по умолчанию (без параметров) будет сгенерирован автоматически на этапе компиляции. Выглядит это, ожидаемо, так:
Это равносильно такой записи:
Но если вы напишите именно так, то вам будет вежливо предложено удалить первичный конструктор без параметров.
Первичный конструктор — тот, который всегда вызывается при создании объекта в случае, если он есть. Пока примем это к сведению, а подробнее разберём позже, когда перейдём к вторичным конструкторам. Соответственно, помним, что если конструкторов нет совсем, то на самом деле один (первичный) есть, но мы его не видим.
Главная особенность первичного конструктора в том, что он не имеет тела, т.е. не может содержать в себе исполняемого кода. Он просто принимает в себя параметры и передаёт их вглубь класса для дальнейшего использования. На уровне синтаксиса это выглядит так:
Параметры, переданные таким образом, могут быть использованы для различных инициализаций, но не более. В чистом виде эти аргументы в рабочем коде класса мы использовать не можем. Однако мы можем проинициализировать поля класса прямо здесь. Это выглядит таким образом:
Здесь param1 и param2 можно использовать в коде в качестве полей класса, что равносильно следующему:
Ну и если сравнивать с Java, то это выглядело бы вот так (и кстати, на этом примере можно оценить, как сильно Kotlin может сократить количество кода):
Т.е. дополнительный конструктор как бы является наследником первичного.
Теперь, если мы создадим объект вызовом дополнительного конструктора, произойдёт следующее:
вызов дополнительного конструктора;
вызов основного конструктора;
инициализация поля класса p1 в основном конструкторе;
выполнение кода в теле дополнительного конструктора.
Это похоже на такую конструкцию в Java:
Вспомним, что в Java мы можем вызвать один конструктор из другого с помощью ключевого слова this только в начале тела конструктора. В Kotlin этот вопрос решили кардинально — сделали такой вызов частью сигнатуры конструктора. На всякий случай отмечу, что вызывать любой (первичный или дополнительный) конструктор прямо из тела дополнительного запрещено.
Дополнительный конструктор всегда должен ссылаться на главный (при его наличии), но может делать это косвенно, ссылаясь на другой дополнительный конструктор. Суть в том, чтобы в конце цепочки мы всё же добрались до главного. Срабатывание конструкторов, очевидно, будет происходить в порядке, обратном обращению конструкторов друг к другу:
Теперь последовательность такая:
Бывает так, что класс не имеет первичного конструктора, при этом может иметь один или несколько дополнительных. Тогда дополнительные конструкторы не обязаны ссылаться на кого-то, но при этом могут ссылаться на другие дополнительные конструкторы этого класса. Ранее мы выяснили, что основной конструктор, не указанный явно, генерируется автоматически, но это касается случаев, когда в классе нет вообще никаких конструкторов. Если есть хотя бы один дополнительный конструктор, первичный конструктор без параметров не создаётся:
Можем создать объект класса вызовом:
Можем создать объект только таким вызовом:
В этом плане в Kotlin по сравнению с Java ничего нового нет.
Кстати, как и первичный конструктор, дополнительный может и не иметь тела в том случае, если его задачей является лишь передача параметров в другие конструкторы.
Также стоит обратить внимание на то, что в отличие от первичного конструктора, инициализация полей класса в списке аргументов дополнительного конструктора запрещена.
Т.е. такая запись будет невалидной:
Отдельно стоит отметить, что дополнительный конструктор, как и первичный, вполне может быть без параметров:
Говоря про конструкторы, нельзя не упомянуть одну из удобных фич Kotlin — возможности назначать для аргументов значения по умолчанию.
Теперь допустим, что мы имеем класс с несколькими конструкторами, имеющими разное количество аргументов. Приведу пример на Java:
Как показывает практика, подобные конструкции встречаются довольно часто. Давайте посмотрим, как то же самое можно написать на Kotlin:
Теперь давайте дружно похлопаем Kotlin за то, как сильно он сократил код. Кстати, кроме уменьшения количества строк мы получаем больше порядка. Вспомните, наверняка вы не раз видели что-то такое:
Когда видишь подобное, хочется найти человека, который это написал, взять за пуговицу, подвести к экрану и грустным голосом спросить: «Зачем?»
Хотя можно повторить такой подвиг и на Kotlin, но не надо.
Есть, правда, одна деталь, которую в случае такой сокращённой записи на Kotlin необходимо учесть: если мы хотим вызывать конструктор со значениями по умолчанию из Java, то мы должны добавить к нему аннотацию @JvmOverloads :
В противном случае мы получим ошибку.
Теперь поговорим про инициализаторы.
В Java также есть инициализирующие блоки, но это не одно и то же. В них мы не можем, как в Kotlin, передать значение извне (аргументы первичного конструктора). Инициализатор очень похож на тело первичного конструктора, вынесенного в отдельный блок. Но это с первого взгляда. На самом деле это не совсем так. Давайте разбираться.
Приведу несколько интересных случаев работы с инициализаторами.
Здесь мы видим, что первое обращение к полю в ходе инициализации (в блоке init или вне его) равносильно обычной его инициализации в Java. Все остальные действия, связанные с присвоением значения в процессе инициализации, кроме первого (первое присвоение значение объединяется с объявлением поля), выносятся в конструктор.
Если проводить эксперименты с декомпиляцией, то выясняется, что если конструктора нет, то генерируется первичный конструктор, и вся магия происходит в нём. Если есть несколько дополнительных конструкторов, не ссылающихся друг на друга, и при этом нет первичного, то в Java-коде этого класса все последующие присвоения значения полю testParam дублируются во всех дополнительных конструкторах. Если же первичный конструктор при этом есть, то только в первичном. Фуф…
И на закуску самое интересное: поменяем сигнатуру testParam с var на val :
А где-то в коде вызовем:
Всё скомпилировалось без ошибок, запустилось, и вот мы видим вывод логов:
in showTestParam testParam = some string
in constructor testParam = after
Делая выводы из приведённых выше случаев, можно только посоветовать не плодить блоки инициализации и не разбрасывать их по классу, избегать повторных присвоений значений в процессе инициализации, вызывать из init-блоков только чистые функции. Всё это делается во избежании возможной путаницы.
Итак. Инициализаторы — это некий блок кода, обязательно выполняемый при создании объекта независимо от того, с помощью какого конструктора этот объект создаётся.
Вроде разобрались. Рассмотрим взаимодействие конструкторов и инициализаторов. В рамках одного класса всё довольно просто, но надо запомнить:
Стоит отметить, что как Object является базовым для всех классов в Java, так Any является таковым в Kotlin. При этом Any и Object 一 это не одно и то же.
Для начала о том, как происходит наследование. Класс-наследник, как и родительский класс, может иметь или не иметь первичного конструктор, но при этом должен ссылаться на определённый конструктор родительского класса.
Если класс-наследник имеет первичный конструктор, то этот конструктор должен указывать на конкретный конструктор базового класса. При этом все дополнительные конструкторы класса-наследника должны ссылаться на основной конструктор своего класса.
Также не забываем про возможность косвенного вызова конструктора родительского класса через другие конструкторы класса-наследника:
Если же класс-наследник не имеет никаких конструкторов, то просто добавляем вызов конструктора родительского класса после имени класса-наследника:
При этом всё же есть вариант с наследованием, при котором ссылка на конструктор родительского класса не требуется. Такая запись является валидной:
Но только в том случае, если у родительского класса есть конструктор без параметров, который является конструктором по умолчанию (первичный или дополнительный — не важно).
Теперь рассмотрим порядок вызова инициализаторов и конструкторов при наследовании:
На этом этапе будем считать, что конструкторы и инициализаторы мы изучили довольно глубоко и теперь знаем про них почти всё. Немножко отдохнём и будем копать в другую сторону!