Functions
В Kotlin функции являются значениями: их можно хранить, передавать и возвращать. На этом основаны callbacks, операции над коллекциями, builders, Compose API и повторно используемый control flow.
Расширения и функциональные значения
Функции-расширения
Функция-расширение позволяет вызывать функцию на существующем типе без наследования и изменения самого типа:
Это лишь простая проверка для UI, а не полноценная валидация email. Важнее то, что расширения разрешаются статически по объявленному типу получателя. Они не имеют доступа к его private- или protected-членам, а совместимая функция-член всегда имеет приоритет над расширением.
Расширения делают API удобнее, но слишком общие расширения для типов вроде String засоряют автодополнение и скрывают доменный смысл. Держите их связными и размещайте рядом с владеющей функциональностью или API.
Лямбды и функциональные типы
Лямбда - это функциональный литерал, который можно сохранить, передать или вернуть:
(A, B) -> C описывает функцию, принимающую A и B и возвращающую C. У () -> Unit нет параметров; suspend () -> T описывает приостанавливаемую функцию; A.(B) -> C имеет получатель A и часто используется в типобезопасных builders и DSL в стиле Compose.
Для единственного выводимого параметра вместо явного имени можно использовать it. Вложенным или нетривиальным лямбдам лучше давать понятные имена параметров. Лямбды могут захватывать внешние переменные; захват изменяемого состояния усложняет рассуждения о lifecycle, конкурентности и recomposition.
Функции высшего порядка
Функция высшего порядка принимает или возвращает функцию:
fun repeatAction(times: Int, action: (index: Int) -> Unit) {
require(times >= 0)
repeat(times) { index -> action(index) }
}
Они отделяют повторно используемый control flow от конкретного поведения. Функциональные значения могут создавать объекты и вызываться косвенно, хотя компилятор и runtime оптимизируют многие случаи. Используйте inline выборочно, когда его семантика или измеримый выигрыш оправданы.
Scope и inline-функции
Scope functions: let / run / with / apply / also
Scope functions различаются главным образом способом доступа к контекстному объекту и возвращаемым значением:
| Функция | Контекст | Возвращает | Типичное назначение |
|---|---|---|---|
let |
it |
Результат лямбды | Null-safe преобразование |
run |
this |
Результат лямбды | Настройка и вычисление результата |
with(obj) |
this |
Результат лямбды | Группа вызовов на существующем объекте |
apply |
this |
Контекстный объект | Настройка объекта |
also |
it |
Контекстный объект | Дополнительный побочный эффект, например логирование |
val user = User().apply {
name = "Ada"
isActive = true
}
val length = user.name.takeIf { it.isNotBlank() }?.length ?: 0
Scope functions не добавляют новых возможностей языку. Выбирайте функцию, которая ясно показывает владельца и возвращаемое значение. Избегайте длинных цепочек, вложенных scopes и неоднозначных ссылок this или it.
inline, noinline и crossinline
Модификатор inline просит компилятор встроить функцию и подходящие лямбда-аргументы в места вызова. Это может устранить расходы на функциональные объекты и виртуальные вызовы, разрешает нелокальные возвраты из подходящих лямбд и позволяет использовать reified. Компилятор может предупредить, если встраивание вряд ли принесёт пользу.
Внутри inline-функции:
noinlineоставляет лямбду обычным значением, чтобы её можно было сохранить или передать туда, где требуется объект.crossinlineзапрещает нелокальныйreturn; он нужен, если лямбда может выполняться из другого контекста, например внутриRunnable.
Встраивание может увеличить объём сгенерированного кода. Оно подходит прежде всего для небольших API высшего порядка и reified-утилит, а не как стандартная оптимизационная аннотация.
reified
Параметр типа может быть reified только у inline-функции:
Поскольку конкретный тип подставляется в месте вызова, внутри можно использовать T::class, is T и API вроде filterIsInstance<T>() без явной передачи Class<T> или KClass<T>.
Это не сохраняет все вложенные аргументы типов во время выполнения. Например, проверка value is List<T> не доказывает runtime-типы всех элементов списка, потому что эти аргументы по-прежнему стираются.