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

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

Шкодим

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

Лямбды

Kotlin поддерживает лямбды. Общий вид выражения.


{ arguments -> function body }

Лямбды всегда находятся внутри фигурных скобок. Слева находятся аргументы, справа - тело функции. Разделяет их специальное выражение ->. Например, создадим выражение.


{x: Int -> x + 5}

Открыли фигурную скобку, записали параметр в виде числа Int, а затем указали, что с ним нужно делать в правой части - прибавить к числу 5. Параметр может быть один, как в нашем пример, несколько или вообще не быть.

Лямбда-выражение можно сохранить в переменной, а затем обращаться к ней как к обычной функции. Можно вызвать лямбда-выражение при помощи invoke().


val lambda = { println("Hello Kitty!") }
lambda.invoke()

Лямбда-выражение может послужить удобной заменой для паттернов Listener или Callback.

Примеры


// Нет аргументов и возвращает 1
{ 1 } // () -> Int

// Один аргумент в виде строки, который выводится на экран
{ s: String -> println(s) }  // (String)->Unit

// два аргумента типа Int и возвращает произведение чисел
{ a: Int, b: Int -> a * b } // (Int, Int)->Int

Код с лямбдами становится короче и читабельнее.


button.setOnClickListener { /* код для щелчка кнопки */ }

Другой пример.


val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2))
// 3

Лямбды можно записывать в несколько строк.


val test = { a: Int, b: Int ->
    println("$a + $b")
	a + b
}

Этот же пример можно записать одной строкой, разделяя команды точкой с запятой.

val test = { a: Int, b: Int -> println("$a + $b"); a + b }

setOnClickListener

Рассмотрим применение лямбда-выражений на примере обработчика щелчка кнопки.

Код на Java 7 и ниже.


button.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View view) {
        /* код для щелчка кнопки */
    }
});

Если использовать такой же код на Kotlin, то получим.


button.setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View?) {
        println("Hello Kitty")
    }
})

Студия предложит сконвертировать код, используя лямбду. В моём случае студия сразу сконвертировала в самый короткий вариант. Но мы рассмотрим все случаи, например, возможен такой вариант.


button.setOnClickListener({ v -> println("Hello Kitty") })

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


button.setOnClickListener() { v -> println("Hello Kitty") }

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

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


button.setOnClickListener { v -> println("Hello Kitty") }

Если в самом лямбда-выражении не используется параметр в левой части, то его можно удалить. Это справедливо для выражения с одним параметром. В нашем примере v (View) не используется, сокращаем код ещё раз.


button.setOnClickListener { println("Hello Kitty") }

Функция с одним параметром. Ключевое слово it

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


button.setOnClickListener { v -> doSomething(v) }

private fun doSomething(v: View?) {
    println("Hello Kitty")
}

Убираем левую часть и используем it.


button.setOnClickListener { doSomething(it) }

Все варианты в одном месте.


button.setOnClickListener({ v -> println("Hello Kitty") })
button.setOnClickListener() { v -> println("Hello Kitty") }
button.setOnClickListener { v -> println("Hello Kitty") }
button.setOnClickListener { println("Hello Kitty") }

button.setOnClickListener { v -> doSomething(v) }
button.setOnClickListener { doSomething(it) }

Другой пример использования - пройтись по элементам коллекции. Например, воспользуемся forEach:


val cats = listOf("Barsik", "Murzik", "Ryzhik")
cats.forEach { println(it) }

// Выводит
Barsik
Murzik
Ryzhik

Без использования ключевого слова it пришлось бы писать длинный вариант.


cats.forEach { cat -> println(cat) }

Функции высшего порядка

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

Напишем функцию convert(), которая преобразует значение Double по формуле, передаваемой в лямбда-выражении, выводит результат и возвращает её. Например, с помощью этой функции вы сможете преобразовать температуру по Цельсию в температуру по Фаренгейту или преобразовать вес из килограммов в фунты — все зависит от формулы, которая передаётся в лямбда-выражении (аргумента). Начнём с определения параметров функции.

Для этого в функцию будут добавлены два параметра: Double и лямбда-выражение. Назовём лямбда-выражение converter, а поскольку оно будет использоваться для преобразования Double в Double, оно должно иметь тип (Double) -> Double (лямбда-выражение, которое получает параметр Double и возвращает Double).


fun convert(x: Double, converter: (Double) -> Double) : Double {
    // ваш код
}

Напишем какой-то для функции.


fun convert(x: Double, converter: (Double) -> Double) : Double {
    val result = converter(x)
    println("$x is converted to $result") // выводим результат
    return result // вернуть результат
}

Функция с лямбда-параметром вызывается точно так же, как и любая другая функция: с передачей значений всех аргументов. В данном случае Double и лямбда-выражений.

Давайте используем функцию convert() для преобразования 20 градусов по Цельсию в градусы по Фаренгейту. Для этого функции нужно передать значения 20.0 и лямбда-выражение { c: Double -> c * 1.8 + 32 }:


convert(20.0, { c: Double -> c * 1.8 + 32 })

// 20.0 is converted to 68.0

// с присвоением возвращаемого результата переменной
val fahrenheit = convert(20.0, { c: Double -> c * 1.8 + 32 })
println(fahrenheit)

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


// было
convert(20.0, { c: Double -> c * 1.8 + 32 })

// стало
convert(20.0) { c: Double -> c * 1.8 + 32 }

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

Предположим, функция convertFive() преобразует Int 5 в Double по формуле преобразования, которая передаётся в виде лямбда-выражения.


fun convertFive(converter: (Int) -> Double) : Double {
    val result = converter(5)
    println("5 is converted to $result")
    return result
}

Вызываем функцию.


// стандартный вариант
convertFive() { it * 1.8 + 32 }

// компактный вариант без круглых скобок
convertFive { it * 1.8 + 32 }

Функция может возвращать лямбда-выражение. Например, следующий код определяет функцию с именем getConversionLambda(), которая возвращает лямбда-выражение типа (Double) -> Double. Точное лямбда-выражение, возвращаемое функцией, зависит от значения переданной строки.


fun getConversionLambda(str: String): (Double) -> Double {
    if (str == "CentigradeToFahrenheit") {
        return { it * 1.8 + 32 }
    } else if (str == "KgsToPounds") {
        return { it * 2.204623 }
    } else if (str == "PoundsToUSTons") {
        return { it / 2000.0 }
    } else {
        return { it }
    }
}

Вы можете вызвать лямбда-выражение, возвращённое функцией, или использовать его в аргументе при вызове другой функции. Например, следующий код выполняет возвращаемое значение getConversionLambda для пересчета 2,5 кг в фунты и присваивает его переменной с именем pounds:


val pounds = getConversionLambda("KgsToPounds")(2.5)

println(pounds)

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

Реклама