1. Información general
En este artículo, exploraremos Lambdas en el lenguaje Kotlin. Tenga en cuenta que las lambdas no son exclusivas de Kotlin y han existido durante muchos años en muchos otros idiomas.
Las expresiones lambdas son funciones esencialmente anónimas que podemos tratar como valores ; por ejemplo, podemos pasarlas como argumentos a métodos, devolverlas o hacer cualquier otra cosa que podamos hacer con un objeto normal.
2. Definición de una Lambda
Como veremos, las Lambdas de Kotlin son muy similares a las Lambdas de Java. Puede obtener más información sobre cómo trabajar con Java Lambdas y algunas de las mejores prácticas aquí.
Para definir una lambda, debemos ceñirnos a la sintaxis:
val lambdaName : Type = { argumentList -> codeBody }
La única parte de una lambda que no es opcional es codeBody.
La lista de argumentos se puede omitir al definir como máximo un argumento y el compilador de Kotlin puede inferir el tipo a menudo. No siempre necesitamos una variable también, la lambda se puede pasar directamente como un argumento de método.
El tipo del último comando dentro de un bloque lambda es el tipo devuelto.
2.1. Inferencia de tipo
La inferencia de tipo de Kotlin permite que el compilador evalúe el tipo de una lambda.
Escribir una lambda que produzca el cuadrado de un número se escribiría así:
val square = { number: Int -> number * number } val nine = square(3)
Kotlin evaluará el ejemplo anterior como una función que toma un Int y devuelve un Int: (Int) -> Int
Si quisiéramos crear una lambda que multiplique sus números de argumento único por 100, luego devuelve ese valor como una cadena:
val magnitude100String = { input : Int -> val magnitude = input * 100 magnitude.toString() }
Kotlin entenderá que esta lambda es de tipo (Int) -> String .
2.2. Declaración de tipo
Ocasionalmente, Kotlin no puede inferir nuestros tipos y debemos declarar explícitamente el tipo de nuestra lambda; al igual que podemos con cualquier otro tipo.
El patrón es entrada -> salida , sin embargo, si el código no devuelve ningún valor usamos el tipo Unidad :
val that : Int -> Int = { three -> three }
val more : (String, Int) -> String = { str, int -> str + int }
val noReturn : Int -> Unit = { num -> println(num) }
Podemos usar lambdas como extensiones de clase:
val another : String.(Int) -> String = { this + it }
El patrón que usamos aquí es ligeramente diferente al de las otras lambdas que hemos definido. Nuestros corchetes todavía contienen nuestros argumentos, pero antes de nuestros corchetes, tenemos el tipo al que vamos a adjuntar esta lambda.
Para usar este patrón de una cadena , llamamos al Type.lambdaName (argumentos) para llamar a nuestro ejemplo 'otro':
fun extendString(arg: String, num: Int) : String { val another : String.(Int) -> String = { this + it } return arg.another(num) }
2.3. Regresando de un Lambda
La expresión final es el valor que se devolverá después de que se ejecute una lambda:
val calculateGrade = { grade : Int -> when(grade) { in 0..40 -> "Fail" in 41..70 -> "Pass" in 71..100 -> "Distinction" else -> false } }
La última forma es aprovechar la definición de función anónima: debemos definir los argumentos y el tipo de retorno explícitamente y podemos usar la declaración de retorno de la misma manera que cualquier método:
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. es
Una forma abreviada de un solo argumento lambda es usar la palabra clave ' it' . Este valor representa cualquier argumento que pasemos a la función lambda.
Realizaremos el mismo método forEach en la siguiente matriz de Ints :
val array = arrayOf(1, 2, 3, 4, 5, 6)
Primero veremos la forma abreviada de la función lambda, seguida de la forma abreviada del mismo código, donde ' it ' representará cada elemento en la siguiente matriz.
Escritura común a mano:
array.forEach { item -> println(item * 4) }
Taquigrafía:
array.forEach { println(it * 4) }
4. Implementación de Lambdas
Cubriremos muy brevemente cómo llamar a una lambda que está dentro del alcance, así como cómo pasar una lambda como argumento.
Una vez que un objeto lambda está dentro del alcance, llámelo como cualquier otro método dentro del alcance, usando su nombre seguido de corchetes y cualquier argumento:
fun invokeLambda(lambda: (Double) -> Boolean) : Boolean { return lambda(4.329) }
Si necesitamos pasar una lambda como argumento a un método de orden superior, tenemos cinco opciones.
4.1. Variable de objeto Lambda
Usando un objeto lambda existente como se declaró en la sección 2, pasamos el objeto al método como lo haríamos con cualquier otro argumento:
@Test fun whenPassingALambdaObject_thenCallTriggerLambda() { val lambda = { arg: Double -> arg == 4.329 } val result = invokeLambda(lambda) assertTrue(result) }
4.2. Lambda Literal
En lugar de asignar el lambda a una variable, podemos pasar el literal directamente a la llamada al método:
Test fun whenPassingALambdaLiteral_thenCallTriggerLambda() { val result = invokeLambda({ true }) assertTrue(result) }
4.3. Lambda literal fuera de los soportes
Otro patrón para los literales lambda alentado por JetBrains es pasar el lambda como último argumento a un método y colocar el lambda fuera de la llamada al método:
@Test fun whenPassingALambdaLiteralOutsideBrackets_thenCallTriggerLambda() { val result = invokeLambda { arg -> arg.isNaN() } assertFalse(result) }
4.4. Referencias de métodos
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:
- The class is an implementation of a Java interface (not a Kotlin one)
- the interface must have max
If both of these conditions are met, we may use a lambda expression instead.
La lambda en sí misma tomará tantos argumentos como el método único de la interfaz.
Un ejemplo común sería usar una lambda en lugar de un consumidor Java estándar :
val list = ArrayList(2) list.stream() .forEach({ i -> println(i) })
7. Conclusión
Si bien son sintácticamente similares, las lambdas de Kotlin y Java son características completamente diferentes. Al apuntar a Java 6, Kotlin debe transformar sus lambdas en una estructura que se pueda utilizar dentro de JVM 1.6.
A pesar de esto, las mejores prácticas de las lambdas de Java 8 aún se aplican.
Más sobre las mejores prácticas de lambda aquí.
Los fragmentos de código, como siempre, se pueden encontrar en GitHub.