Expressões Lambda em Kotlin
1. Visão geral
Neste artigo, vamos explorar Lambdas na linguagem Kotlin. Lembre-se de que as lambdas não são exclusivas do Kotlin e existem há muitos anos em muitos outros idiomas.
Lambdas Expressions are essentially anonymous functions that we can treat as values – podemos, por exemplo, passá-los como argumentos para métodos, retorná-los ou fazer qualquer outra coisa que poderíamos fazer com um objeto normal.
2. Definindo um Lambda
Como veremos, Kotlin Lambdas são muito semelhantes aos Java Lambdas. Você pode descobrir mais sobre como trabalhar com Java Lambdas e algumas práticas recomendadashere.
Para definir um lambda, precisamos seguir a sintaxe:
val lambdaName : Type = { argumentList -> codeBody }
A única parte de um lambda que não é opcional é o codeBody.
A lista de argumentos pode ser ignorada ao definir no máximo um argumento e a varreduraType geralmente pode ser inferida pelo compilador Kotlin. We don’t always need a variable as well, the lambda can be passed directly as a method argument.
O tipo do último comando dentro de um bloco lambda é o tipo retornado.
2.1. Inferência de tipo
A inferência de tipo de Kotlin permite que o tipo de lambda seja avaliado pelo compilador.
Escrever uma lambda que produz o quadrado de um número seria tão escrito quanto:
val square = { number: Int -> number * number }
val nine = square(3)
Kotlin irá avaliar o exemplo acima como uma função que leva umInt areia retorna umInt:(Int) → Int
Se quisermos criar um lambda que multiplique seus números de argumento único por 100, então retorne esse valor como umString:
val magnitude100String = { input : Int ->
val magnitude = input * 100
magnitude.toString()
}
Kotlin entenderá que este lambda é do tipo(Int) → String.
2.2. Declaração de tipo
Ocasionalmente, Kotlin não pode inferir nossos tipos e devemos declarar explicitamente o tipo para nosso lambda; assim como podemos com qualquer outro tipo.
O padrão éinput → output, no entanto, se o código não retornar nenhum valor, usamos o tipoUnit:
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 extensões de classe:
val another : String.(Int) -> String = { this + it }
O padrão que usamos aqui é um pouco diferente dos outros lambdas que definimos. Nossos colchetes ainda contêm nossos argumentos, mas antes de nossos colchetes, temos o tipo ao qual vamos anexar este lambda.
Para usar esse padrão deString we, chameType.lambdaName(arguments)so para chamar nosso "outro" exemplo:
fun extendString(arg: String, num: Int) : String {
val another : String.(Int) -> String = { this + it }
return arg.another(num)
}
2.3. Retornando de um Lambda
A expressão final é o valor que será retornado após a execução de um lambda:
val calculateGrade = { grade : Int ->
when(grade) {
in 0..40 -> "Fail"
in 41..70 -> "Pass"
in 71..100 -> "Distinction"
else -> false
}
}
A maneira final é aproveitar a definição de função anônima - devemos definir os argumentos e o tipo de retorno explicitamente e podemos usar a instrução de retorno da mesma forma que qualquer método:
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
Uma forma abreviada de um único argumento lambda é usar a palavra-chave ‘it'. This value represents any lone that argument we pass to the lambda function.
Vamos realizar o mesmo métodoforEach na seguinte matriz deInts:
val array = arrayOf(1, 2, 3, 4, 5, 6)
Veremos primeiro a forma extensa da função lambda, seguida pela forma abreviada do mesmo código, onde ‘it’ representará cada elemento na seguinte matriz.
À mão:
array.forEach { item -> println(item * 4) }
Forma abreviada:
array.forEach { println(it * 4) }
4. Implementando Lambdas
Cobriremos muito brevemente como chamar um lambda que está no escopo e também como passar um lambda como um argumento.
Quando um objeto lambda estiver no escopo, chame-o como qualquer outro método no escopo, usando seu nome seguido por colchetes e quaisquer argumentos:
fun invokeLambda(lambda: (Double) -> Boolean) : Boolean {
return lambda(4.329)
}
Se precisarmos passar um lambda como argumento para um método de ordem superior, teremos cinco opções.
4.1. Variável de Objeto Lambda
Usando um objeto lambda existente conforme declarado na seção 2, passamos o objeto para o método como faríamos com qualquer outro argumento:
@Test
fun whenPassingALambdaObject_thenCallTriggerLambda() {
val lambda = { arg: Double ->
arg == 4.329
}
val result = invokeLambda(lambda)
assertTrue(result)
}
4.2. Lambda Literal
Em vez de atribuir o lambda a uma variável, podemos passar o literal diretamente para a chamada do método:
Test
fun whenPassingALambdaLiteral_thenCallTriggerLambda() {
val result = invokeLambda({
true
})
assertTrue(result)
}
4.3. Literal Lambda Fora dos Suportes
Outro padrão para literais lambda incentivado pelo JetBrains - é passar o lambda como o último argumento para um método e colocar o lambda fora da chamada do método:
@Test
fun whenPassingALambdaLiteralOutsideBrackets_thenCallTriggerLambda() {
val result = invokeLambda { arg -> arg.isNaN() }
assertFalse(result)
}
4.4. Referências de método
Finalmente, temos a opção de usar referências de método. Estas são referências a métodos existentes.
Em nosso exemplo abaixo, pegamosDouble::isFinite. Essa função, então, assume a mesma estrutura de um lambda, no entanto, é do tipoKFunction1<Double, Boolean> as ela tem um argumento, recebe umDouble e retorna umBoolean:
@Test
fun whenPassingAFunctionReference_thenCallTriggerLambda() {
val reference = Double::isFinite
val result = invokeLambda(reference)
assertTrue(result)
}
5. Kotlin Lambda em Java
O Kotlin usa interfaces de função geradas para interoperar com Java. Eles existem no código-fonte do Kotlinhere.
Temos um limite no número de argumentos que podem ser passados com essas classes geradas. O limite atual é 22; representado pela interfaceFunction22.
A estrutura dos genéricos de uma sinterfaceFunction é que o número e representa o número de argumentos para o lambda, então esse número de classes será o argumento Tipos em ordem.
O argumento genérico final é o tipo de retorno:
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. O código Java deve importar a classe do pacotekotlin e retornar comnull.
Abaixo está um exemplo de chamada de um Kotlin Lambda de um projeto que faz parte do Kotlin e parte do Java:
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
...
new Function1() {
@Override
public Unit invoke(Customer c) {
AnalyticsManager.trackFacebookLogin(c.getCreated());
return null;
}
}
Ao usar Java8, usamos um lambda Java em vez de uma classeFunction anonymous:
@Test
void givenJava8_whenUsingLambda_thenReturnLambdaResult() {
assertTrue(LambdaKt.takeLambda(c -> c >= 0));
}
6. Classes internas anônimas
Kotlin tem duas maneiras interessantes de trabalhar com classes internas anônimas.
6.1. Expressão de Objeto
Ao chamar uma Classe Anônima Interna Kotlin ou uma Classe Anônima Java composta de vários métodos, devemos implementar uma Expressão de Objeto.
Para demonstrar isso, vamos pegar uma interface simples e uma classe que pega uma implementação dessa interface e chama os métodos dependentes de um argumentoBoolean :
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()
}
}
}
Agora, para fornecer uma classe interna anônima, precisamos usar a sintaxe "objeto":
@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. Expressão Lambda
Por outro lado, também podemos ter a opção de usar um lambda. Using lambdas in lieu of an Anonymous Inner Class has certain conditions:
-
A classe é uma implementação de uma interface Java (não da Kotlin)
-
a interface deve ter no máximo
Se essas duas condições forem atendidas, podemos usar uma expressão lambda.
O lambda em si terá tantos argumentos quanto o método único da interface.
Um exemplo comum seria usar um lambda em vez de um JavaConsumer: padrão
val list = ArrayList(2) list.stream() .forEach({ i -> println(i) })
7. Conclusion
Embora sintaticamente semelhantes, Kotlin e Java lambdas são recursos completamente diferentes. Ao direcionar o Java 6, o Kotlin deve transformar seus lambdas em uma estrutura que possa ser utilizada na JVM 1.6.
Apesar disso, as melhores práticas do Java 8 lambdas ainda se aplicam.
Mais sobre as melhores práticas de lambdahere.
Trechos de código, como sempre, podem ser encontradosover on GitHub.