Лямбда-выражения в Котлин
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:с
-
Класс является реализацией интерфейса Java (не Kotlin)
-
интерфейс должен иметь макс
Если оба эти условия выполнены, мы можем вместо этого использовать лямбда-выражение.
Сама лямбда будет принимать столько аргументов, сколько и единственный метод интерфейса.
Типичный пример - использование лямбда вместо стандартного 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.