The original link
Some of Kotlin’s standard functions are so similar that we are not sure which one to use. Here I will introduce a simple method to clearly distinguish their differences and how to choose to use them.
The scope of the function
I focused on the run, with, t. run, T. et, T. lso and t. ply functions. I call them scope functions because I think their main function is to provide an internal scope for calling functions.
The run function is the simplest scope method to illustrate
fun test(a) {
var mood = "I am sad"
run {
val mood = "I am happy"
println(mood) // I am happy
}
println(mood) // I am sad
}
Copy the code
With this function, you can have a separate scope inside the test function, and the mood is completely enclosed within the run scope before being redefined to I am happy and printed.
This range function by itself doesn’t seem very useful. But another nice thing about scopes is that they return the last object in the scope.
Therefore, the following code will be pure, and we can apply the show() method to two views as shown below, instead of calling it twice.
run {
if (firstTimeView) introView else normalView
}.show()
Copy the code
Three properties of the scope function
To make scope functions more interesting, let me classify their behavior with three attributes, and use those attributes to distinguish them.
1. Normal vs. extended functions
If we look at the definition, with and t. run the two functions are actually very similar. The following example implements the same function.
with(webview.settings) {
javaScriptEnabled = true
databaseEnabled = true
}
/ / similar
webview.settings.run {
javaScriptEnabled = true
databaseEnabled = true
}
Copy the code
However, they differ in that with is a normal function and t. run is an extension function.
So the question is, what are the advantages of each?
Imagine if webView.Settings could be empty, it would look something like this.
with(webview.settings) {
this? .javaScriptEnabled =true
this? .databaseEnabled =true} } webview.settings? .run { javaScriptEnabled =true
databaseEnabled = true
}
Copy the code
In this case, the t. run extension is obviously better because we can null before using it.
2.This vs. it parameters
If we look at the definitions, t run and t et these two functions are almost identical except for the way they take arguments. The logic of the following two functions is the same.
stringVariable? .run { println("The length of this String is $length") } stringVariable? .let { println("The length of this String is ${it.length}")}Copy the code
If you check the t.run function signature, you’ll notice that t.run only calls block: t. () as an extension function. So, for all ranges, T can be called this. In programming, this can be omitted most of the time. So, in our example above, we could use $length in the println declaration instead of ${this.length}. I call this passing the this parameter.
However, for t.et function signatures, you’ll notice that t.et passes itself in as an argument, namely block: (T). Therefore, this is like passing a lambda argument. It can use IT as a reference in scope. So I call this passing it parameters.
From above, it seems that t. run is superior because t. et is more implicit, but it is the t. et function that has some subtle advantages as follows:
T.let
Using a given variable function/member provides a clearer distinction than using an external class function/member- in
this
Cannot be omitted, for example when it is passed as an argument to a functionit
thanthis
Shorter, clearer. - in
T.let
Allows for better variable names that you can convertit
Is another name.
stringVariable? .let { nonNullString -> println("The non null string is $nonNullString")}Copy the code
3. Return current type vs. other type
Now, let’s look at t. et and T.also. If we look at their internal function range, they work the same
stringVariable? .let { println("The length of this String is ${it.length}") } stringVariable? .also { println("The length of this String is ${it.length}")}Copy the code
However, they differ subtly in their return values. T returns a different type of value, while t. also returns T itself, which is this.
Both are useful for linking functions, with t.et you can evolve operations, and with t.also you perform operations on the same variable this.
Here’s a simple example
val original = "abc"
// Change the value and pass it to the next chain
original.let {
println("The original String is $it") // "abc"
it.reversed() // Change the parameter and pass it to the next chain
}.let {
println("The reverse String is $it") // "cba"
it.length // Change the type
}.let {
println("The length of the String is $it") / / 3
}
/ / error
// Send the same value in the chain (printed answer is wrong)
original.also {
println("The original String is $it") // "abc"
it.reversed() // Even if we change it, it won't work
}.also {
println("The reverse String is ${it}") // "abc"
it.length // Even if we change it, it won't work
}.also {
println("The length of the String is ${it}") // "abc"
}
// Also can be done by modifying the original string
// Send the same value in the chain
original.also {
println("The original String is $it") // "abc"
}.also {
println("The reverse String is ${it.reversed()}") // "cba"
}.also {
println("The length of the String is ${it.length}") / / 3
}
Copy the code
From above, t.also seems pointless because we can easily combine them into a feature block. But if you think about it, it also has some advantages:
- It can provide a very clear separation process on the same object, making smaller functional parts.
- Before use, it can achieve very powerful self-manipulation, implementing chain Builder operation (Builder mode).
When the two are combined, a self-evolution, a self-preservation, can become very powerful, as in the following
// The normal method
fun makeDir(path: String): File {
val result = File(path)
result.mkdirs()
return result
}
// Improve the method
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }
Copy the code
All the properties
By illustrating these three properties, we should be able to understand the behavior of these functions. Let’s take a look at the t. ply function again, since it’s not mentioned above. These three attributes are defined in t.apply as follows…
- This is an extension function
- the
this
Passed as a parameter. - It returns
this
(itself)
Therefore, it could conceivably be used as follows
// The normal method
fun createInstance(args: Bundle) : MyFragment {
val fragment = MyFragment()
fragment.arguments = args
return fragment
}
// Improve the method
fun createInstance(args: Bundle)
= MyFragment().apply { arguments = args }
Copy the code
Or we can create chained calls.
// The normal method
fun createIntent(intentData: String, intentAction: String): Intent {
val intent = Intent()
intent.action = intentAction
intent.data=Uri.parse(intentData)
return intent
}
// Improve implementation
fun createIntent(intentData: String, intentAction: String) =
Intent().apply { action = intentAction }
.apply { data = Uri.parse(intentData) }
Copy the code
Function to choose
So, obviously, with these three properties, we can now classify the above functions accordingly. Based on this, we can form a decision tree below that can help us decide which function to use.
Hopefully, the above decision tree clarifies the differences between functions, simplifies your decisions, and enables you to use these functions appropriately.