Desejo ter um bom exemplo para cada função executada, deixe, aplique, também, com
Eu li este artigo, mas ainda falta um exemplo
Desejo ter um bom exemplo para cada função executada, deixe, aplique, também, com
Eu li este artigo, mas ainda falta um exemplo
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 this
e, portanto seed
, podemos definir , hash
e hashRepetitions
sem 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 this
ainda 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 it
agora 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 apply
aqui seria sombra this
, então isso this.weight
se referiria à maçã, e não à cesta de frutas.
Nota: Eu descaradamente peguei os exemplos do meu blog
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.
Observe também o gráfico que acompanha minha explicação abaixo.
Conceito
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 fazervar 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 cadeiarun { }
como o seguintemyComputer.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, maswith { }
não é. Então você chamarun { }
ethis
dentro 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 entrerun {}
ewith {}
.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.A maneira de olhar para isso
Eu tendo a olhar para
also
elet
como algo que é externo, externo. Sempre que você diz essas duas palavras, é como se você tentasse agir de acordo com algo.let
instale vírus neste computador ealso
bloqueie-o. Então, isso define se você é um ator ou não.Para a parte do resultado, está claramente lá.
also
expressa 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/with
claramente 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 :)
fonte
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.
Portanto, para encontrar apenas números no
String
, você pode criar um método como o abaixo sem herdar aString
classe.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
pode ser qualquer objeto como a classe String. portanto, você pode invocar essa função com qualquer objeto.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
also
leva 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 stringphoneNumber
que corresponde apenas aregex
. 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()) }
number
terá uma string dephoneNumber
somente se não corresponder aregex
. 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
fonte
phoneNumber. takeUnless{}
, em vez dephoneNumber. takeIf{}
.Existem 6 funções de escopo diferentes:
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)
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
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
fonte
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 (escolhaapply
/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,this
pode ser omitida, quando é uma expressão de parâmetro,it
é menor quethis
: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 :
fonte