Освой Kotlin играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Анонимные функции

В Kotlin часто используется особый вид функций - анонимные функции. Функции можно не давать имя после ключевого слова fun и она станет анонимной.


val a: (Int) -> Int = fun(i: Int) = i + 3
println(a(4)) // 7

Анонимная функция прибавляет к заданному значению число 3.

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

В стандартной библиотеке есть функция count() для подсчёта числа символов в строке.


val charNumber = "Васька".count()
println(charNumber) // выводит 6

Есть перегруженная версия функции, в которой можно вызвать анонимную функцию в качестве аргумента. Зададим новое правило - подсчитать количество символов а в этом же слове.


val charNumber = "Васька".count({letter -> letter == 'а'})
println(charNumber) // выводит 2

Функция count() использует анонимную функцию, чтобы решить, как считать символы в строке. Она последовательно передаёт анонимной функции символ за символом, и если та вернёт истинное значение для заданного символа, общий счёт нужных символов увеличивается на единицу. Как только будет проверен последний символ, функция count() возвратит итоговое значение. Это одно из применений анонимных функций.

Если заглянуть под капот, то увидим, что функция принимает анонимную функцию в аргументе типа (Char) -> Boolean с именем predicate. Функция predicate принимает аргумент Char и возвращает булево значение.


public inline fun CharSequence.count(
    predicate: (Char) → Boolean
): Int

Создадим собственную анонимную функцию для вывода какого-то сообщения.


println(
        {
            val year = 2019
            "Добро пожаловать в $year год"
        }()
)

Между фигурными скобками находится тело анонимной функции. После закрывающей фигурной скобкой ставится пара круглых скобок, как и положено функциям. В нашем примере используются пустые круглые скобки, но возможен вариант, когда требуются аргументы.

Анонимные функции имеют специальный функциональный тип. Переменные этого типа могут содержать анонимные функции и использоваться как обычные переменные.

Объявим переменную, которая будет содержать обычную функцию, которая в свою очередь будет иметь анонимную функцию.


val messageFunction: () -> String =
        {
            val year = 2019
            "Добро пожаловать в $year год"
        }

println(messageFunction())

Разберём синтаксис. Сначала объявляется переменная messageFunction. После имени переменной идёт двоеточие и функциональный тип. Функциональный тип объявляется в виде конструкции () -> String (вместо String могут быть другие типы, в зависимости от задачи) и сообщает компилятору, какой тип функции содержится в переменной.В нашем случае мы можем использовать любую функцию без аргументов (пустые скобки), которая возвращает строку.

У анонимных функции не используется ключевое слово return, однако функция неявно и автоматически возвращает результат последней инструкции в теле функции.

Пример можно написать без определения типа, полагаясь на автоматическое определение типов.


val messageFunction =
        {
            val year = 2019
            "Добро пожаловать в $year год"
        }

println(messageFunction())

Разберём случаи с аргументами. Перепишем предыдущий пример, добавив один аргумент.


val messageFunction: (String) -> String =
        {
            catName ->
            val year = 2019
            "$catName! Добро пожаловать в $year год"
        }

println(messageFunction("Васька"))

Мы указали, что анонимная функция принимает строку. Имя строкового параметра определяется внутри функции, сразу после открывающей фигурной скобки, и за ним следует стрелка.

Здесь также можно использовать автоматическое определение типов и сократить код. Но в этом случае у параметров следует указать тип.


val messageFunction =
        {
            catName: String ->
            val year = 2019
            "$catName! Добро пожаловать в $year год"
        }

println(messageFunction("Васька"))

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

Ключевое слово it

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

Переделаем предыдущий пример. Удаляем имя параметра и стрелку в начале анонимной функции и добавляем it.


val messageFunction: (String) -> String =
        {
            val year = 2019
            "$it! Добро пожаловать в $year год"
        }

println(messageFunction("Васька"))

Ключевое слово it удобно тем, что не требует имени переменной. Но с другой стороны при сложном коде страдает читаемость кода. Нужно соблюдать баланс и использовать подобную возможность в коротких выражениях, где понимание кода не вызовет трудностей:


// другой вариант примера из начала статьи
val charNumber = "Васька".count({it -> it == 'а'})
println(charNumber) // выводит 2

// оптимизированный вариант без круглых скобок
val charNumber = "Васька".count { it -> it == 'а'}
println(charNumber) // выводит 2

Несколько аргументов

Напомню, что it можно использовать только для одного аргумента. Если анонимная функция использует несколько аргументов, то используйте именованные переменные.


val messageFunction: (String, Int) -> String =
        { catName, age ->
            val year = 2019
            "$catName! Добро пожаловать в $year год. Вам исполнилось $age лет"
        }

println(messageFunction("Васька", 5))

Выражение теперь объявляет два параметра, catName и age, и принимает два аргумента. Так как теперь больше одного параметра в выражении, ключевое слово it использовать нельзя.

Подобного рода анонимные функции называют лямбдами, и им посвящена ещё одна статья.

Дополнительное чтение

Реклама