Ламбда изрази в Котлин

1. Общ преглед

В тази статия ще изследваме Lambdas на езика Kotlin. Имайте предвид, че ламбдите не са уникални за Kotlin и съществуват от много години на много други езици.

Lambdas Expressions са по същество анонимни функции, които можем да третираме като стойности - можем например да ги предадем като аргументи на методи, да ги върнем или да направим каквото и да било друго нещо, което бихме могли да направим с нормален обект.

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

Както ще видим, Kotlin Lambdas много приличат на Java Lambdas. Можете да научите повече за това как да работите с Java Lambdas и някои най-добри практики тук.

За да определим ламбда, трябва да се придържаме към синтаксиса:

val lambdaName : Type = { argumentList -> codeBody }

Единствената част от ламбда, която не е задължителна, е codeBody.

Списъкът аргументи могат да бъдат пропуснати при определянето на най-много един аргумент и тип често може да се изведе от компилатора Kotlin. Не винаги се нуждаем и от променлива, ламбда може да бъде предадена директно като аргумент на метод.

Типът на последната команда в рамките на ламбда блок е върнатият тип.

2.1. Тип извод

Типичното заключение на Kotlin позволява видът на ламбда да бъде оценен от компилатора.

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

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

Kotlin ще оцени горния пример като функция, която взема един Int и връща Int: (Int) -> Int

Ако искахме да създадем ламбда, която умножава единичните си аргументи по 100, тогава връща тази стойност като низ:

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

Котлин ще разбере, че тази ламбда е от тип (Int) -> String .

2.2. Декларация за тип

Понякога Котлин не може да направи изводи за нашите типове и трябва изрично да декларираме типа за нашата ламбда; точно както можем с всеки друг тип.

Шаблонът е вход -> изход , но ако кодът не връща стойност, използваме типа 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, ние извикваме Type.lambdaName (аргументи), за да извикаме нашия „друг“ пример:

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  100) { return "Error" } else if (grade < 40) { return "Fail" } else if (grade < 70) { return "Pass" } return "Distinction" }

3. то

Стенография на един аргумент ламбда е да се използва ключовата дума „ то“ . Тази стойност представлява всеки самотен аргумент, който предаваме на ламбда функцията.

Ще изпълним същия метод forEach на следния масив от Ints :

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

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

Longhand:

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

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

array.forEach { println(it * 4) }

4. Прилагане на Lambdas

Ще разгледаме накратко как да наречем ламбда, която е в обхвата, както и как да предадем ламбда като аргумент.

След като ламбда обект е в обхват, извикайте го като всеки друг метод в обхвата, като използвате името му, последвано от скоби и всякакви аргументи:

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. Lambda Literal

Вместо да присвоим ламбда на променлива, можем да предадем литерала директно в извикването на метода:

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. Референции на методите

Finally, we have the option of using method references. These are references to existing methods.

In our example below, we take Double::isFinite. That function then takes on the same structure as a lambda, however, it's of type KFunction1 as it has one argument, takes in a Double and returns a Boolean:

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

5. Kotlin Lambda in Java

Kotlin uses generated function interfaces to interop with Java. They exist in the Kotlin source code here.

We have a limit on the number of arguments that can be passed in with these generated classes. The current limit is 22; represented by the interface Function22.

The structure of a Function interface's generics is that the number and represents the number of arguments to the lambda, then that number of classes will be the argument Types in order.

The final generic argument is the return type:

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. The Java code must import the class from the kotlin package and return with null.

Below is an example of calling a Kotlin Lambda from a project that is part Kotlin and part Java:

import kotlin.Unit; import kotlin.jvm.functions.Function1; ... new Function1() { @Override public Unit invoke(Customer c) { AnalyticsManager.trackFacebookLogin(c.getCreated()); return null; } } 

When using Java8, we use a Java lambda instead of a Function anonymous class:

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

6. Anonymous Inner Classes

Kotlin has two interesting ways of working with Anonymous Inner Classes.

6.1. Object Expression

When calling a Kotlin Inner Anonymous Class or a Java Anonymous Class comprised of multiple methods we must implement an Object Expression.

To demonstrate this, we'll take a simple interface and a class that takes an implementation of that interface and calls the methods dependent on a Boolean argument:

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() } } }

Now to provide an anonymous inner class, we need to use the “object” syntax:

@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. Lambda Expression

On the other hand, we may also have the option of using a lambda instead. Using lambdas in lieu of an Anonymous Inner Class has certain conditions:

  1. The class is an implementation of a Java interface (not a Kotlin one)
  2. the interface must have max

If both of these conditions are met, we may use a lambda expression instead.

Самата ламбда ще вземе толкова аргументи, колкото единичния метод на интерфейса.

Често срещан пример би бил използването на ламбда вместо стандартен потребител на Java :

val list = ArrayList(2) list.stream() .forEach({ i -> println(i) })

7. Заключение

Докато синтактично сходни, ламбдите Kotlin и Java са напълно различни характеристики. Когато се насочва към Java 6, Kotlin трябва да трансформира своите ламбда в структура, която може да се използва в рамките на JVM 1.6.

Въпреки това, най-добрите практики на Java 8 ламбди все още се прилагат.

Повече за най-добрите практики за ламбда тук.

Кодови фрагменти, както винаги, можете да намерите в GitHub.