Exemplo de quando devemos usar run, let, apply, also and with on Kotlin

100

Desejo ter um bom exemplo para cada função executada, deixe, aplique, também, com

Eu li este artigo, mas ainda falta um exemplo

UmAnusorn
fonte

Respostas:

121

Todas essas funções são usadas para mudar o escopo da função / variável atual. Eles são usados ​​para manter as coisas que pertencem juntas em um só lugar (principalmente inicializações).

aqui estão alguns exemplos:

run - retorna o que você quiser e reajusta o escopo da variável para a qual é usado this

val password: Password = PasswordGenerator().run {
       seed = "someString"
       hash = {s -> someHash(s)}
       hashRepetitions = 1000

       generate()
   }

O gerador de senha agora foi refeito como thise, portanto seed, podemos definir , hashe hashRepetitionssem usar uma variável. generate()retornará uma instância de Password.

applyé semelhante, mas retornará this:

val generator = PasswordGenerator().apply {
       seed = "someString"
       hash = {s -> someHash(s)}
       hashRepetitions = 1000
   }
val pasword = generator.generate()

Isso é particularmente útil como um substituto para o padrão Builder e se você quiser reutilizar certas configurações.

let- usado principalmente para evitar verificações nulas, mas também pode ser usado como um substituto para run. A diferença é que thisainda será o mesmo de antes e você acessa a variável com novo escopo usando it:

val fruitBasket = ...

apple?.let {
  println("adding a ${it.color} apple!")
  fruitBasket.add(it)
}

O código acima adicionará a maçã à cesta somente se ela não for nula. Observe também que itagora não é mais opcional, então você não encontrará um NullPointerException aqui (ou seja, você não precisa usar ?.para acessar seus atributos)

also- use-o quando quiser apply, mas não quer sombrearthis

class FruitBasket {
    private var weight = 0

    fun addFrom(appleTree: AppleTree) {
        val apple = appleTree.pick().also { apple ->
            this.weight += apple.weight
            add(apple)
        }
        ...
    }
    ...
    fun add(fruit: Fruit) = ...
}

Usar applyaqui seria sombra this, então isso this.weightse referiria à maçã, e não à cesta de frutas.


Nota: Eu descaradamente peguei os exemplos do meu blog

Lovis
fonte
3
Para qualquer pessoa como eu que se assustou com o primeiro código, a última linha de um lambda é considerada um valor de retorno em Kotlin.
Jay Lee
62

Existem mais alguns artigos como este , e aqui, que vale a pena dar uma olhada.

Eu acho que é quando você precisa de um mais curto, mais conciso dentro de algumas linhas, e para evitar ramificação ou verificação de instrução condicional (como se não for nula, então faça isso).

Adoro este gráfico simples, por isso o vinculei aqui. Você pode ver isso como escrito por Sebastiano Gottardo.

insira a descrição da imagem aqui

Observe também o gráfico que acompanha minha explicação abaixo.

Conceito

Eu acho que é uma forma de jogar papel dentro do seu bloco de código quando você chama essas funções + se você quiser voltar (para encadear funções de chamada ou definir como variável de resultado, etc).

Acima está o que eu acho.

Exemplo de conceito

Vamos ver exemplos para todos eles aqui

1.) myComputer.apply { }significa que você deseja atuar como um ator principal (você quer pensar que é o computador), e você se quer de volta (computador) para que possa fazer

var crashedComputer = myComputer.apply { 
    // you're the computer, you yourself install the apps
    // note: installFancyApps is one of methods of computer
    installFancyApps() 
}.crash()

Sim, você mesmo acabou de instalar os aplicativos, travar e salvar-se como referência para permitir que outros vejam e façam algo com eles.

2.) myComputer.also {}significa que você tem certeza de que não é um computador, mas sim um estranho que deseja fazer algo com ele e também o deseja como um resultado retornado.

var crashedComputer = myComputer.also { 
    // now your grandpa does something with it
    myGrandpa.installVirusOn(it) 
}.crash()

3.) with(myComputer) { }significa que você é o ator principal (computador) e não se quer como resultado.

with(myComputer) {
    // you're the computer, you yourself install the apps
    installFancyApps()
}

4.) myComputer.run { }significa que você é o ator principal (computador) e não se quer como resultado.

myComputer.run {
    // you're the computer, you yourself install the apps
    installFancyApps()
}

mas é diferente with { }em um sentido muito sutil que você pode chamar em cadeia run { }como o seguinte

myComputer.run {
    installFancyApps()
}.run {
    // computer object isn't passed through here. So you cannot call installFancyApps() here again.
    println("woop!")
}

Isso se deve a run {}sua função de extensão, mas with { }não é. Então você chama run { }e thisdentro do bloco de código será refletido para o tipo de objeto do chamador. Você pode ver isso como uma excelente explicação para a diferença entre run {}e with {}.

5.) myComputer.let { }significa que você é um estranho que olha para o computador e quer fazer algo a respeito sem qualquer cuidado para que a instância do computador seja devolvida para você novamente.

myComputer.let {
    myGrandpa.installVirusOn(it)
}

A maneira de olhar para isso

Eu tendo a olhar para alsoe letcomo algo que é externo, externo. Sempre que você diz essas duas palavras, é como se você tentasse agir de acordo com algo. letinstale vírus neste computador e alsobloqueie-o. Então, isso define se você é um ator ou não.

Para a parte do resultado, está claramente lá. alsoexpressa que também é outra coisa, então você ainda mantém a disponibilidade do próprio objeto. Portanto, ele o retorna como resultado.

Tudo o mais está associado this. Além disso, run/withclaramente não tem interesse em devolver o eu-objeto de volta. Agora você pode diferenciar todos eles.

Acho que às vezes, quando nos afastamos de exemplos 100% baseados em programação / lógica, estamos em melhor posição para conceituar as coisas. Mas isso depende certo :)

haxpor
fonte
1
O diagrama conta tudo; o melhor até agora.
Shukant Pal de
Esta deve ser a resposta aceita e mais votada
Segun Wahaab
8

let, também, apply, takeIf, takeUnless são funções de extensão em Kotlin.

Para entender essas funções, você precisa entender as funções de extensão e as funções Lambda no Kotlin.

Função de extensão:

Pelo uso da função de extensão, podemos criar uma função para uma classe sem herdar uma classe.

Kotlin, semelhante a C # e Gosu, fornece a capacidade de estender uma classe com novas funcionalidades sem ter que herdar da classe ou usar qualquer tipo de padrão de design, como Decorator. Isso é feito por meio de declarações especiais chamadas extensões. Kotlin oferece suporte a funções de extensão e propriedades de extensão.

Portanto, para encontrar apenas números no String, você pode criar um método como o abaixo sem herdar a Stringclasse.

fun String.isNumber(): Boolean = this.matches("[0-9]+".toRegex())

você pode usar a função de extensão acima assim,

val phoneNumber = "8899665544"
println(phoneNumber.isNumber)

que é impressões true.

Funções Lambda:

As funções Lambda são como a Interface em Java. Mas no Kotlin, as funções lambda podem ser passadas como um parâmetro nas funções.

Exemplo:

fun String.isNumber(block: () -> Unit): Boolean {
    return if (this.matches("[0-9]+".toRegex())) {
        block()
        true
    } else false
}

Você pode ver que o bloco é uma função lambda e é passado como um parâmetro. Você pode usar a função acima assim,

val phoneNumber = "8899665544"
    println(phoneNumber.isNumber {
        println("Block executed")
    })

A função acima será impressa assim,

Block executed
true

Espero que agora você tenha uma ideia sobre as funções de extensão e funções do Lambda. Agora podemos ir para as funções de extensão uma por uma.

deixei

public inline fun <T, R> T.let(block: (T) -> R): R = block(this)

Dois tipos T e R usados ​​na função acima.

T.let

Tpode ser qualquer objeto como a classe String. portanto, você pode invocar essa função com qualquer objeto.

block: (T) -> R

No parâmetro let, você pode ver a função lambda acima. Além disso, o objeto de chamada é passado como um parâmetro da função. Portanto, você pode usar o objeto de classe invocando dentro da função. em seguida, ele retorna o R(outro objeto).

Exemplo:

val phoneNumber = "8899665544"
val numberAndCount: Pair<Int, Int> = phoneNumber.let { it.toInt() to it.count() }

No exemplo acima, vamos pegar String como um parâmetro de sua função lambda e retornar Pair em retorno.

Da mesma forma, outra função de extensão funciona.

Além disso

public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }

a função de extensão alsoleva a classe de chamada como um parâmetro de função lambda e não retorna nada.

Exemplo:

val phoneNumber = "8899665544"
phoneNumber.also { number ->
    println(number.contains("8"))
    println(number.length)
 }

Aplique

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

O mesmo que também, mas o mesmo objeto de chamada passado como a função para que você possa usar as funções e outras propriedades sem chamá-lo ou o nome do parâmetro.

Exemplo:

val phoneNumber = "8899665544"
phoneNumber.apply { 
    println(contains("8"))
    println(length)
 }

Você pode ver no exemplo acima as funções da classe String invocadas diretamente dentro da função lambda.

takeIf

public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null

Exemplo:

val phoneNumber = "8899665544"
val number = phoneNumber.takeIf { it.matches("[0-9]+".toRegex()) }

No exemplo acima number, terá uma string phoneNumberque corresponde apenas a regex. Caso contrário, será null.

takeUnless

public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null

É o inverso de takeIf.

Exemplo:

val phoneNumber = "8899665544"
val number = phoneNumber.takeUnless { it.matches("[0-9]+".toRegex()) }

numberterá uma string de phoneNumbersomente se não corresponder a regex. Caso contrário, será null.

Você pode ver respostas semelhantes, o que é útil aqui, a diferença entre kotlin também, apply, let, use, takeIf e takeUnless em Kotlin

Bhuvanesh BS
fonte
Você tem um erro de digitação no seu último exemplo, você provavelmente quis dizer phoneNumber. takeUnless{}, em vez de phoneNumber. takeIf{}.
Ryan Amaral
1
Corrigido. Obrigado @Ryan Amaral
Bhuvanesh BS
5

Existem 6 funções de escopo diferentes:

  1. T.run
  2. T.let
  3. T.apply
  4. T. também
  5. com
  6. corre

Eu preparei uma nota visual como a abaixo para mostrar as diferenças:

data class Citizen(var name: String, var age: Int, var residence: String)

insira a descrição da imagem aqui

A decisão depende de suas necessidades. Os casos de uso de diferentes funções se sobrepõem, para que você possa escolher as funções com base nas convenções específicas usadas em seu projeto ou equipe.

Embora as funções de escopo sejam uma forma de tornar o código mais conciso, evite usá-las em excesso: elas podem diminuir a legibilidade do código e levar a erros. Evite aninhar funções de escopo e tenha cuidado ao encadea-las: é fácil ficar confuso sobre o objeto de contexto atual e o valor deste ou daquele.

Aqui está outro diagrama para decidir qual usar em https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84 insira a descrição da imagem aqui

Algumas convenções são as seguintes:

Use também para ações adicionais que não alteram o objeto, como registrar ou imprimir informações de depuração.

val numbers = mutableListOf("one", "two", "three")
 numbers
 .also { println("The list elements before adding new one: $it") }
 .add("four")

O caso comum de aplicação é a configuração do objeto.

val adam = Person("Adam").apply {
age = 32
city = "London"        
}
println(adam)

Se você precisar de sombreamento, use run

fun test() {
    var mood = "I am sad"

    run {
        val mood = "I am happy"
        println(mood) // I am happy
    }
    println(mood)  // I am sad
}

Se você precisar retornar o próprio objeto receptor, use aplicar ou também

Oiyio
fonte
3

De acordo com minha experiência, uma vez que tais funções são sintáticas inline, sem diferença de desempenho, você deve sempre escolher aquela que requer a escrita da menor quantidade de código no lamda.

Para fazer isso, primeiro determine se você deseja que o lambda retorne seu resultado (escolha run/ let) ou o próprio objeto (escolha apply/ also); então, na maioria dos casos, quando o lambda é uma expressão única, escolha aqueles com o mesmo tipo de função de bloco dessa expressão, porque quando é uma expressão receptora, thispode ser omitida, quando é uma expressão de parâmetro, ité menor que this:

val a: Type = ...

fun Type.receiverFunction(...): ReturnType { ... }
a.run/*apply*/ { receiverFunction(...) } // shorter because "this" can be omitted
a.let/*also*/ { it.receiverFunction(...) } // longer

fun parameterFunction(parameter: Type, ...): ReturnType { ... }
a.run/*apply*/ { parameterFunction(this, ...) } // longer
a.let/*also*/ { parameterFunction(it, ...) } // shorter because "it" is shorter than "this"

No entanto, quando o lambda consiste em uma mistura deles, cabe a você escolher aquele que se encaixa melhor no contexto ou com o qual você se sente mais confortável.

Além disso, use aqueles com função de bloco de parâmetro quando a desconstrução for necessária:

val pair: Pair<TypeA, TypeB> = ...

pair.run/*apply*/ {
    val (first, second) = this
    ...
} // longer
pair.let/*also*/ { (first, second) -> ... } // shorter

Aqui está uma breve comparação entre todas essas funções do curso Kotlin oficial da JetBrains no Coursera Kotlin para desenvolvedores Java : Tabela de diferenças Implementações simplificadas

Shreck Ye
fonte