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
Respostas:
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.
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.
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 this
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 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)
}
Eu tendo a olhar para also
e let
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 e also
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 :)
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 String
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.let
T
pode 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 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 string phoneNumber
que 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()) }
number
terá uma string de phoneNumber
somente 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
phoneNumber. takeUnless{}
, em vez de phoneNumber. 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
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, this
pode 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 :