Перейти к содержанию

Functions

В Kotlin функции являются значениями: их можно хранить, передавать и возвращать. На этом основаны callbacks, операции над коллекциями, builders, Compose API и повторно используемый control flow.

Расширения и функциональные значения

Функции-расширения

Функция-расширение позволяет вызывать функцию на существующем типе без наследования и изменения самого типа:

fun String.looksLikeEmail(): Boolean =
    contains("@") && substringAfterLast("@").contains(".")

Это лишь простая проверка для UI, а не полноценная валидация email. Важнее то, что расширения разрешаются статически по объявленному типу получателя. Они не имеют доступа к его private- или protected-членам, а совместимая функция-член всегда имеет приоритет над расширением.

Расширения делают API удобнее, но слишком общие расширения для типов вроде String засоряют автодополнение и скрывают доменный смысл. Держите их связными и размещайте рядом с владеющей функциональностью или API.

Лямбды и функциональные типы

Лямбда - это функциональный литерал, который можно сохранить, передать или вернуть:

val transform: (Int) -> Int = { value -> value * 2 }
val doubled = numbers.map(transform)

(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-функции:

inline fun <reified T> Any?.isType(): Boolean = this is T

Поскольку конкретный тип подставляется в месте вызова, внутри можно использовать T::class, is T и API вроде filterIsInstance<T>() без явной передачи Class<T> или KClass<T>.

Это не сохраняет все вложенные аргументы типов во время выполнения. Например, проверка value is List<T> не доказывает runtime-типы всех элементов списка, потому что эти аргументы по-прежнему стираются.

Связанные темы

Источники