Dominar as funções padrão do Kotlin: run, with, let, also e apply
Algumas das funções padrão do Kotlin são tão semelhantes que não temos certeza de qual usar. Aqui, apresentarei uma maneira simples de distinguir claramente suas diferenças e como escolher qual usar.
Conteudo
Funções de escopo
As funções nas quais irei me concentrar são run, with, T.run, T.let, T.also e T.apply. Eu as chamo de funções de escopo, pois vejo que sua função principal é fornecer um escopo interno para a função do chamador.
A maneira mais simples de ilustrar o escopo é a função de execução
fun test() { var mood = "Estou triste" run { val mood = "Estou feliz" println(mood) // Estou feliz } println(mood) // estou triste }
Com isso, dentro da função de “test”, você poderia ter um escopo separado, onde o “mood” é redefinido para “Estou feliz” antes de imprimir, e é totalmente incluído no escopo de execução.
Esta função de definição de escopo em si não parece muito útil. Mas há outra parte legal que tem mais do que apenas o escopo; ele retorna algo, ou seja, o último objeto dentro do escopo.
Conseqüentemente, o abaixo seria legal, por meio do qual podemos aplicar o show() a ambas as visualizações, sem chamá-lo duas vezes.
run { if (firstTimeView) introView else normalView }.show()
3 atributos de funções de escpo
Para tornar as funções de escopo mais interessantes, deixe-me categorizar seu comportamento com 3 atributos. Usarei esses atributos para distingui-los uns dos outros.
1. Função normal vs. extensão
Se olharmos para ‘with’ e T.run, ambas as funções são bastante semelhantes. O seguinte faz a mesma coisa.
with(webview.settings) { javaScriptEnabled = true databaseEnabled = true } // Similar webview.settings.run { javaScriptEnabled = true databaseEnabled = true }
No entanto, sua diferença é que uma é uma função normal, ou seja, “with”, enquanto a outra é uma função de extensão, ou seja, T.run.
Portanto, a questão é: qual é a vantagem de cada um?
Imagine se webview.settings pudesse ser nulo, eles teriam a aparência abaixo.
// Uau! with(webview.settings) { this?.javaScriptEnabled = true this?.databaseEnabled = true } // Agradável. webview.settings?.run { javaScriptEnabled = true databaseEnabled = true }
Nesse caso, a função de extensão T.run é melhor, pois poderíamos aplicar para verificar se há nulidade antes de usá-la.
2. Este argumento contra isso
Se olharmos para T.run e T.let, ambas as funções são semelhantes, exceto por uma coisa, a maneira como aceitam o argumento. O seguinte mostra a mesma lógica para ambas as funções.
stringVariable?.run { println("O comprimento desta string é $length") } // Similarly. stringVariable?.let { println("O comprimento desta string é ${it.length}") }
Se você verificar a assinatura da função T.run, notará que T.run é feito apenas como um bloco de chamada de função de extensão: T. (). Conseqüentemente, tudo dentro do escopo, o T poderia ser referido como this. Na programação, isso poderia ser omitido na maioria das vezes. Portanto, em nosso exemplo acima, poderíamos usar $length na instrução println, em vez de $ {this.length}. Eu chamo isso de enviar isso como um argumento.
No entanto, para a assinatura da função T.let, você notará que T.let está se enviando para a função, ou seja, “block: (T)”. Portanto, isso é como um argumento lambda enviado. Ele pode ser referido na função de escopo como ‘it’. Portanto, chamo isso de enviar como um argumento.
Do exposto acima, parece que T.run é mais superior sobre T.let, pois é mais implícito, mas existem algumas vantagens sutis da função T.let conforme abaixo: –
- O T.let fornece uma distinção mais clara entre o uso da função/membro da variável fornecida e a função / membro da classe externa
- No caso de “this” não poder ser omitido, por exemplo quando “it” é enviado como parâmetro de uma função, é mais curto de escrever do que “this” e mais claro.
- O T.let permite nomear melhor a variável usada convertida, ou seja, você pode converter “it” para outro nome.
stringVariable?.let { nonNullString -> println("A string não nula é $nonNullString") }
3. Retorne “this” contra outros ‘types”
Agora, vamos olhar para T.let e T. também, ambos são idênticos, se olharmos para o escopo de função interna dele.
stringVariable?.let { println("O comprimento desta string é ${it.length}") } // Exatamente o mesmo que abaixo stringVariable?.also { println("O comprimento desta string é ${it.length}") }
No entanto, sua diferença sutil é o que eles retornam. O T.let retorna um tipo diferente de valor, enquanto “T.also” retorna o próprio T, ou seja, “this”.
Ambos são úteis para funções de encadeamento, em que “T.let” permite que você evolua a operação e “T.also” permite que você execute a mesma variável, ou seja, this.
Ilustração simples como abaixo
val original = "abc" // Evolua o valor e envie para a próxima cadeia original.let { println("A string original é $it") // "abc" it.reversed() // evolve it as parameter to send to next let }.let { println("A string reversa é $it") // "cba" it.length // pode evoluir para outro tipo }.let { println("O comprimento da corda é $it") // 3 } // errado // O mesmo valor é enviado na cadeia (a resposta impressa está errada) original.also { println("A string original é$it") // "abc" it.reversed() // mesmo que o evoluamos, é inútil }.also { println("A string reversa é ${it}") // "abc" it.length // mesmo que o evoluamos, é inútil }.also { println("O comprimento da corda é ${it}") // "abc" } // Corrigido para também (ou seja, manipular como string original // O mesmo valor é enviado na cadeia original.also { println("A string original é $it") // "abc" }.also { println("A string reversa é ${it.reversed()}") // "cba" }.also { println("The length of the String is ${it.length}") // 3 }
O T.also pode parecer sem sentido acima, já que poderíamos facilmente combiná-los em um único bloco de função. Pensando bem, tem algumas boas vantagens
- Pode fornecer um processo de separação muito claro nos mesmos objetos, ou seja, fazer seções funcionais menores.
- Pode ser muito poderoso para auto-manipulação antes de ser usado, fazendo uma operação de construtor de encadeamento.
Quando ambos combinam a cadeia, ou seja, um se desenvolve, o outro se mantém, torna-se algo poderoso, e. abaixo
// Normal approach fun makeDir(path: String): File { val result = File(path) result.mkdirs() return result } // Abordagem aprimorada fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }
Olhando para todos os atributos
Observando os 3 atributos, podemos saber muito bem o comportamento da função. Deixe-me ilustrar a função T.apply, já que ela não foi mencionada acima. Os 3 atributos de T.apply são os seguintes …
- É uma função de extensão.
- Envia this como seu argumento.
- Ele retorna this (ou seja, ele mesmo)
Portanto, usando-o, pode-se imaginar, ele poderia ser usado como
// Normal approach fun createInstance(args: Bundle) : MyFragment { val fragment = MyFragment() fragment.arguments = args return fragment } // Improved approach fun createInstance(args: Bundle) = MyFragment().apply { arguments = args }
Ou também podemos tornar a criação de objetos desencadeados capaz de ser encadeada.
// Normal approach fun createIntent(intentData: String, intentAction: String): Intent { val intent = Intent() intent.action = intentAction intent.data=Uri.parse(intentData) return intent } // Improved approach, chaining fun createIntent(intentData: String, intentAction: String) = Intent().apply { action = intentAction } .apply { data = Uri.parse(intentData) }
Seleção de funções
Portanto, claramente, com os 3 atributos, podemos agora categorizar as funções de acordo. E com base nisso, poderíamos formar uma árvore de decisão abaixo que poderia ajudar a decidir qual função queremos usar dependendo do que precisamos.
Esperamos que a árvore de decisão acima esclareça as funções e também simplifique sua tomada de decisão, permitindo que você domine o uso dessas funções de maneira adequada.
Sinta-se à vontade para fornecer um bom exemplo real de como você usa essas funções em resposta a este blog. Eu adoraria ouvir de você. Isso pode beneficiar outras pessoas.