Лямбда-выражения в Котлин

Лямбда-выражения в Котлин

1. обзор

В этой статье мы собираемся изучить лямбды на языке Котлин. Имейте в виду, что лямбды не являются уникальными для Kotlin и существуют уже много лет на многих других языках.

Lambdas Expressions are essentially anonymous functions that we can treat as values – мы можем, например, передавать их в качестве аргументов методам, возвращать их или делать что-то еще, что мы могли бы сделать с обычным объектом.

2. Определение лямбды

Как мы увидим, Kotlin Lambdas очень похожи на Java Lambdas. Вы можете узнать больше о том, как работать с Java Lambdas, и о некоторых передовых методахhere.

Чтобы определить лямбду, нам нужно придерживаться синтаксиса:

val lambdaName : Type = { argumentList -> codeBody }

Единственная необязательная часть лямбда - это codeBody.

Список аргументов можно пропустить при определении не более одного аргумента, а сканированиеType часто выполняется компилятором Kotlin. We don’t always need a variable as well, the lambda can be passed directly as a method argument.

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

2.1. Вывод типа

Вывод типа Kotlin позволяет компилятору оценивать тип лямбды.

Написание лямбды, которая производит квадрат числа, будет выглядеть так:

val square = { number: Int -> number * number }
val nine = square(3)

Kotlin оценит приведенный выше пример как функцию, которая принимает одинInt and и возвращаетInt:(Int) → Int

Если бы мы хотели создать лямбду, которая умножает числа своих единственных аргументов на 100, а затем возвращает это значение какString:

val magnitude100String = { input : Int ->
    val magnitude = input * 100
    magnitude.toString()
}

Котлин поймет, что эта лямбда имеет тип(Int) → String.

2.2. Объявление типа

Иногда Kotlin не может определить наши типы, и мы должны явно объявить тип для нашей лямбды; так же, как и с любым другим типом.

Шаблон -input → output, однако, если код не возвращает значения, мы используем типUnit:

val that : Int -> Int = { three -> three }
val more : (String, Int) -> String = { str, int -> str + int }
val noReturn : Int -> Unit = { num -> println(num) }

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

val another : String.(Int) -> String = { this + it }

Шаблон, который мы здесь используем, немного отличается от других лямбд, которые мы определили. Наши скобки по-прежнему содержат наши аргументы, но перед скобками у нас есть тип, к которому мы собираемся присоединить эту лямбду.

Чтобы использовать этот шаблон изString we, вызовитеType.lambdaName(arguments)so для вызова нашего «другого» примера:

fun extendString(arg: String, num: Int) : String {
    val another : String.(Int) -> String = { this + it }

    return arg.another(num)
}

2.3. Возвращаясь из лямбды

Последнее выражение - это значение, которое будет возвращено после выполнения лямбды:

val calculateGrade = { grade : Int ->
    when(grade) {
        in 0..40 -> "Fail"
        in 41..70 -> "Pass"
        in 71..100 -> "Distinction"
        else -> false
    }
}

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

val calculateGrade = fun(grade: Int): String {
    if (grade < 0 || grade > 100) {
        return "Error"
    } else if (grade < 40) {
        return "Fail"
    } else if (grade < 70) {
        return "Pass"
    }

    return "Distinction"
}

3. itс

Сокращение лямбда с одним аргументом - использование ключевого слова «it'. This value represents any lone that argument we pass to the lambda function.

Мы выполним тот же методforEach  для следующего массиваInts:

val array = arrayOf(1, 2, 3, 4, 5, 6)

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

обыкновенное письмо:

array.forEach { item -> println(item * 4) }

Стенография:

array.forEach { println(it * 4) }

4. Реализация лямбда-выражений

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

Как только лямбда-объект окажется в области видимости, вызовите его как любой другой метод в области действия, используя его имя, сопровождаемое скобками и любые аргументы:

fun invokeLambda(lambda: (Double) -> Boolean) : Boolean {
    return lambda(4.329)
}

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

4.1. Переменная лямбда-объекта

Используя существующий лямбда-объект, как объявлено в разделе 2, мы передаем объект в метод, как и с любым другим аргументом:

@Test
fun whenPassingALambdaObject_thenCallTriggerLambda() {
    val lambda = { arg: Double ->
        arg == 4.329
    }

    val result = invokeLambda(lambda)

    assertTrue(result)
}

4.2. Лямбда Литерал

Вместо того, чтобы назначать лямбду переменной, мы можем передать литерал непосредственно в вызов метода:

Test
fun whenPassingALambdaLiteral_thenCallTriggerLambda() {
    val result = invokeLambda({
        true
    })

    assertTrue(result)
}

4.3. Лямбда буквальный вне скобок

Еще один шаблон для лямбда-литералов, поддерживаемый JetBrains - это передача лямбда-функции в качестве последнего аргумента методу и размещение лямбда-выражения вне вызова метода:

@Test
fun whenPassingALambdaLiteralOutsideBrackets_thenCallTriggerLambda() {
    val result = invokeLambda { arg -> arg.isNaN() }

    assertFalse(result)
}

4.4. Ссылки на метод

Наконец, у нас есть возможность использовать ссылки на методы. Это ссылки на существующие методы.

В нашем примере ниже мы беремDouble::isFinite. Затем эта функция принимает ту же структуру, что и лямбда, но имеет типKFunction1<Double, Boolean> , так как имеет один аргумент, принимаетDouble и возвращаетBoolean:

@Test
fun whenPassingAFunctionReference_thenCallTriggerLambda() {
    val reference = Double::isFinite
    val result = invokeLambda(reference)

    assertTrue(result)
}

5. Котлин Лямбда на Яве

Kotlin использует сгенерированные интерфейсы функций для взаимодействия с Java. Они существуют в исходном коде Kotlinhere.

У нас есть ограничение на количество аргументов, которые могут быть переданы с этими сгенерированными классами. Текущее ограничение - 22; представлен интерфейсомFunction22.

Структура дженериков синтерфейсаFunction состоит в том, что число и представляет количество аргументов лямбда-выражения, тогда это количество классов будет по порядку типами аргументов.

Последний универсальный аргумент - это тип возвращаемого значения:

import kotlin.jvm.functions.*

public interface Function1 : Function {
    public operator fun invoke(p1: P1): R
}

When there is no return type defined within the Kotlin code, then the lambda returns a Kotlin Unit Код Java должен импортировать класс из пакетаkotlin и возвращать его сnull.

Ниже приведен пример вызова Kotlin Lambda из проекта, который является частью Kotlin и частью Java:

import kotlin.Unit;
import kotlin.jvm.functions.Function1;
...
new Function1() {
    @Override
    public Unit invoke(Customer c) {

        AnalyticsManager.trackFacebookLogin(c.getCreated());

        return null;
    }
}

При использовании Java8 мы используем лямбда-выражение Java вместо санонимного классаFunction :

@Test
void givenJava8_whenUsingLambda_thenReturnLambdaResult() {
    assertTrue(LambdaKt.takeLambda(c -> c >= 0));
}

6. Анонимные Внутренние Классы

У Kotlin есть два интересных способа работы с анонимными внутренними классами.

6.1. Выражение объекта

При вызове внутреннего анонимного класса Kotlin или анонимного Java-класса, состоящего из нескольких методов, мы должны реализовать выражение объекта.

Чтобы продемонстрировать это, мы возьмем простой интерфейс и класс, который реализует этот интерфейс и вызывает методы, зависящие от аргументаBoolean :

class Processor {
    interface ActionCallback {
        fun success() : String
        fun failure() : String
    }

    fun performEvent(decision: Boolean, callback : ActionCallback) : String {
        return if(decision) {
            callback.success()
        } else {
            callback.failure()
        }
    }
}

Теперь, чтобы предоставить анонимный внутренний класс, нам нужно использовать синтаксис «объект»:

@Test
fun givenMultipleMethods_whenCallingAnonymousFunction_thenTriggerSuccess() {
    val result = Processor().performEvent(true, object : Processor.ActionCallback {
        override fun success() = "Success"

        override fun failure() = "Failure"
    })

    assertEquals("Success", result)
}

6.2. Лямбда-выражение

С другой стороны, у нас также может быть возможность использовать лямбду. Using lambdas in lieu of an Anonymous Inner Class has certain conditions:с

  1. Класс является реализацией интерфейса Java (не Kotlin)

  2. интерфейс должен иметь макс

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

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

Типичный пример - использование лямбда вместо стандартного JavaConsumer:

val list = ArrayList(2)

list.stream()
  .forEach({ i -> println(i) })

7. Conclusionс

Синтаксически сходные лямбды Kotlin и Java - это совершенно разные функции. Ориентируясь на Java 6, Kotlin должен преобразовать свои лямбды в структуру, которую можно использовать в JVM 1.6.

Несмотря на это, лучшие практики Java 8 лямбда-прежнему применяются.

Подробнее о передовых методах лямбда-выраженияhere.

Фрагменты кода, как всегда, можно найтиover on GitHub.