No retreat, only to see the way out! – jian-jun wei
In the above chapter, the syntax and application of Lambda expressions in Kotlin are explained in detail. The article has mentioned Kotlin’s higher-order functions for many times, so this article brings you all aspects of Kotlin’s higher-order functions.
directory
I. Introduction of higher-order functions
In Kotlin, higher-order functions are those that use a function as an argument or return value to a function.
1.1. Higher-order functions in cases where functions are used as function parameters
Here’s the sumBy{} higher-order function in strings. Take a look at the source code
// sumBy function source code
public inline fun CharSequence.sumBy(selector: (Char) -> Int) :Int {
var sum: Int = 0
for (element in this) {
sum += selector(element)
}
return sum
}
Copy the code
Source code note:
- You don’t have to worry about it here
inline
, andsumBy
Before the functionCharSequence.
. Because this isKoltin
In theInline function
withExtend the functionality
. This will be explained in a later chapter. Here we focus on higher-order functions, so we don’t do much analysis here. - This function returns one
Int
Type. And accepted oneselector()
Function as an argument to the function. Among them,selector()
The function takes aChar
Type, and return oneInt
Type. - To define a
sum
Variable, and loop through the string, calling it onceselector()
Theta plus thetasum
. Used as a summation. Among themthis
The keyword represents the string itself.
So this function converts every character in the string to an Int, which is used for summation, and returns the summation
Ex. :
val testStr = "abc"
val sum = testStr.sumBy { it.toInt() }
println(sum)
Copy the code
The output is:
294 // Since character A corresponds to 97,b corresponds to 98, and c corresponds to 99, 97 + 98 + 99 = 294
Copy the code
A higher-order function that uses a function as the return value of a function
Here is an example from the official website. Lock () function, take a look at his source code implementation
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
Copy the code
Source code note:
- It’s used in this
kotlin
In theThe generic
The knowledge point, here does not consider. I’ll explain that in a future post. - As you can see from the source, this function accepts one
Lock
Type as a parameter1
And accepts a no-parameter return type ofT
Function as an argument2
. - The return value of this function is a function, we can look at this line of code
return body()
You can see that.
Example: using the lock function, the following code is pseudocode, I am according to the official website of the example directly taken to use
fun toBeSynchronized(a) = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized)
Copy the code
::toBeSynchronized is a reference to the function toBeSynchronized(), and the use of double colons :: is not discussed or explained here.
The above can also be written:
val result = lock(lock, {sharedResource.operation()} )
Copy the code
1.3. Use of higher-order functions
In the two examples above, we have str.sumby {it.toint}. In fact, this kind of writing was explained in the previous chapter on using Lambda. Here is a shorthand for Lambda syntax in higher-order functions.
From the above example we should write like this:
str.sumBy( { it.toInt } )
Copy the code
But according to the convention in Kotlin, when a function has only one function as an argument, and you use a lambda expression as the corresponding argument, you can omit the function’s parentheses (). Therefore, we can write:
str.sumBy{ it.toInt }
Copy the code
There is also a convention that when the last argument to a function is a function and you pass a lambda expression as the corresponding argument, you can specify it outside of parentheses. So the code in example 2 above can be written as:
val result = lock(lock) {
sharedResource.operation()
}
Copy the code
2. Customize higher-order functions
Let’s start with an example:
/ / the source code
fun test(a : Int , b : Int) : Int{
return a + b
}
fun sum(num1 : Int , num2 : Int) : Int{
return num1 + num2
}
/ / call
test(10,sum(3.5)) // The result is 18
// lambda
fun test(a : Int , b : (num1 : Int , num2 : Int) -> Int) : Int{
return a + b.invoke(3.5)
}
/ / call
test(10,{ num1: Int, num2: Int -> num1 + num2 }) // The result is 18
Copy the code
As you can see, in the code above, I write the value directly in the body of my method, which is very unreasonable in development, and will not write this way. The above example merely illustrates the syntax of Lambda. Let me give you another example:
Example: Pass two arguments and a function to implement their different logic
private fun resultByOpt(num1 : Int , num2 : Int , result : (Int ,Int) -> Int) : Int{
return result(num1,num2)
}
fun main(a) {
val result1 = resultByOpt(1.2) {
num1, num2 -> num1 + num2
}
val result2 = resultByOpt(3.4) {
num1, num2 -> num1 - num2
}
val result3 = resultByOpt(5.6) {
num1, num2 -> num1 * num2
}
val result4 = resultByOpt(6.3) {
num1, num2 -> num1 / num2
}
println("result1 = $result1")
println("result2 = $result2")
println("result3 = $result3")
println("result4 = $result4")
}
Copy the code
The output is:
result1 = 3
result2 = -1
result3 = 30
result4 = 2
Copy the code
This example implements the +, -, *, / of two numbers, depending on the different Lambda expressions passed in.
Three, commonly used standard higher-order function introduction
Here are a few standard higher-order functions commonly used in Kotlin. Skilled use of the following several functions, can reduce a lot of code, and increase the readability of code. The source code for the following higher-order functions comes almost entirely from the standard.kt file
3.1. TODO function
This function is not a higher-order function, it is just a normal function that throws exceptions and tests for errors.
The NotImplementedError () function displays a NotImplementedError error. The NotImplementedError Error class inherits from Java Error. We have a look at his source code to know:
public class NotImplementedError(message: String = "An operation is not implemented.") : Error(message)
Copy the code
TODO function source
@kotlin.internal.InlineOnly
public inline fun TODO(a): Nothing = throw NotImplementedError()
@kotlin.internal.InlineOnly
public inline fun TODO(reason: String): Nothing =
throw NotImplementedError("An operation is not implemented: $reason")
Copy the code
For example:
fun main(args: Array<String>) {
TODO("Test TODO to see if it throws an error.")
}
Copy the code
The output is:
When TODO() is called without An argument, An operation is not implemented is printed.
3.2. Run () function
Run function here is divided into two cases, because in the source code is also divided into two functions to achieve. Using different run functions can have different effects.
3.2.1, the run ()
Let’s take a look at the source code:
public inline fun <R> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
Copy the code
Xiao Sheng does not quite understand the meaning of the code contract. On some big blog it is said that its editor inferred the context. But I don’t know if it’s right, because it’s not explained on the website either. But this word means contract, contract, etc. I think it has something to do with that. I won’t go into that here. I’ll focus on the use of the run{} function and its implications.
All we care about here is the return block() line. From the source, we can see that the run function simply executes our block(), a Lambda expression, and returns the result of the execution.
Use 1:
This function is used when we need to execute a block of code, and the block is independent. That is, I can write some code in the run() function that is independent of the project, because it will not affect the normal operation of the project.
Example: Used in a function
private fun testRun1(a) {
val str = "kotlin"
run{
val str = "java" // Does not conflict with the above variables
println("str = $str")
}
println("str = $str")
}
Copy the code
Output result:
str = java
str = kotlin
Copy the code
Use 2:
Because the run function executes the lambda expression I passed in and returns the result of the execution, when a business logic needs to execute the same piece of code, different results can be determined based on different conditions. You can use the run function
Example: get the length of the string.
val index = 3
val num = run {
when(index){
0 -> "kotlin"
1 -> "java"
2 -> "php"
3 -> "javaScript"
else -> "none"
}
}.length
println("num = $num")
Copy the code
The output is:
num = 10
Copy the code
3.2.2, T.r UN ()
The t.run () function and run() function are almost the same, about the difference between the two we look at the source code implementation to understand:
public inline fun <T, R> T.run(block: T. () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
Copy the code
Block () is an extension of a function of type T. This means that my block() function can use the context of the current object. So we can use this function when our passed lambda expression wants to use the context of the current object.
Ex. :
val str = "kotlin"
str.run {
println( "length = ${this.length}" )
println( "first = ${first()}")
println( "last = The ${last()}" )
}
Copy the code
The output is:
length = 6
first = k
last = n
Copy the code
In this case, you can use the this keyword, because in this case it codes for the STR object, or you can omit it. Block () is an extension function of type T.
This can be used in actual development as follows:
Example: Set properties for TextView.
val mTvBtn = findViewById<TextView>(R.id.text)
mTvBtn.run{
text = "kotlin"
textSize = 13f
.
}
Copy the code
The with() function
In fact, with() function and t. run() function function is the same, we look at its implementation source code:
public inline fun <T, R> with(receiver: T, block: T. () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
Copy the code
The difference between these two functions is:
with
Is a normal function of higher order,T.run()
Is an extended higher-order function.with
The return value of the function is specifiedreceiver
For the receiver.
Example: Implement an example of the t.run () function above
val str = "kotlin"
with(str) {
println( "length = ${this.length}" )
println( "first = ${first()}")
println( "last = The ${last()}" )
}
Copy the code
The output is:
length = 6
first = k
last = n
Copy the code
Example: Look at the convenience between the two functions when my object is nullable
val newStr : String? = "kotlin"
with(newStr){
println( "length = The ${this? .length}" )
println( "first = The ${this? .first()}")
println( "last = The ${this? .last()}" )
}
newStr? .run {
println( "length = $length" )
println( "first = ${first()}")
println( "last = ${last()}" )
}
Copy the code
As we can see from the above code, using t.run () is much more readable and concise than using with() when using nullable objects. Of course, how to choose to use these two functions depends on the actual needs and preferences.
3.4. T.apply() function
T. ply();
public inline fun <T> T.apply(block: T. () -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
Copy the code
From the source of t.ply () and the source of t.run () mentioned above, we can conclude that the logic of the two functions is similar, the only difference is that T,apply after executing the block() function, return its own object. While t. run returns the result of execution.
Therefore: the function of t. ply can realize the function of t. run, but also the subsequent operation. Let’s look at an example
Example: After setting TextView properties, set click events, etc
val mTvBtn = findViewById<TextView>(R.id.text)
mTvBtn.apply{
text = "kotlin"
textSize = 13f
.
}.apply{
// Here you can go ahead and set properties or some other TextView operations
}.apply{
setOnClickListener{ .... }
}
Copy the code
Or: Set to Fragment to set data passing
// The original method
fun newInstance(id : Int , name : String , age : Int) : MimeFragment{
val fragment = MimeFragment()
fragment.arguments? .putInt("id",id)
fragment.arguments? .putString("name",name)
fragment.arguments? .putInt("age",age)
return fragment
}
// Improve the method
fun newInstance(id : Int , name : String , age : Int) = MimeFragment().apply {
arguments = Bundle()
arguments? .putInt("id",id)
arguments? .putString("name",name)
arguments? .putInt("age",age)
}
Copy the code
3.5. T.also() function
In terms of the t. so function, it is very similar to t. ply. Let’s take a look at the implementation of its source code:
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
Copy the code
We can see that the parameter block function in t. also passes in its own object. So this function calls its own object with the block function, and then returns its own object
Here is a simple example and an example to illustrate the difference between it and T. ply
Ex. :
"kotlin".also {
println("The result:${it.plus("-java")}")
}.also {
println("The result:${it.plus("-php")}")
}
"kotlin".apply {
println("The result:The ${this.plus("-java")}")
}.apply {
println("The result:The ${this.plus("-php")}")
}
Copy the code
Their output is the same:
Results: the kotlin - Java
Results: the kotlin - PHP
Results: the kotlin - Java
Results: the kotlin - PHP
Copy the code
As we can see from the above example, the difference is that t.also can only call itself with it, while t.apply can only call itself with this. T.also returns itself after executing block(this) in the source code. T.apply returns itself after block(). Is this the key to why you can use it in some functions and only this in others
3.6. T. et() function
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
Copy the code
From the above source we can see that it is actually similar to t. also and T. pply. And the role of T. et is not only in the use of air safety this point. Other operations can also be implemented with T. et
Ex. :
"kotlin".let {
println("Original string:$it") // kotlin
it.reversed()
}.let {
println("Invert the value of the string:$it") // niltok
it.plus("-java")
}.let {
println("New string:$it") // niltok-java
}
"kotlin".also {
println("Original string:$it") // kotlin
it.reversed()
}.also {
println("Invert the value of the string:$it") // kotlin
it.plus("-java")
}.also {
println("New string:$it") // kotlin
}
"kotlin".apply {
println("Original string:$this") // kotlin
this.reversed()
}.apply {
println("Invert the value of the string:$this") // kotlin
this.plus("-java")
}.apply {
println("New string:$this") // kotlin
}
Copy the code
Output the same result as the comment:
Original string: kotlin
Reverse the value after the string: niltok
New string: niltok- Java
Original string: kotlin
Value after reversing the string: kotlin
New string: kotlin
Original string: kotlin
Value after reversing the string: kotlin
New string: kotlin
Copy the code
3.7. T. takeif () function
From the name of the function we can see that this is a conditional judgment function, we are looking at its source implementation:
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
contract {
callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
}
return if (predicate(this)) this else null
}
Copy the code
This function is used to pass in a condition that you want, return itself if the object meets your condition, or null if it does not
Example: Checks whether a string starts with a character, returns itself if the condition is true, and null if not
val str = "kotlin"
val result = str.takeIf {
it.startsWith("ko")
}
println("result = $result")
Copy the code
The output is:
result = kotlin
Copy the code
3.8. The t.unless () function
This function does the same thing as the t.takeif () function. It’s just the opposite of the logic. That is, pass in a condition you want, return null if the object meets your condition, and return itself otherwise.
Here take a look at its source code to understand.
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
contract {
callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
}
return if(! predicate(this)) this else null
}
Copy the code
T.takeif () is the exact opposite of t.takeif ().
Ex. :
val str = "kotlin"
val result = str.takeUnless {
it.startsWith("ko")
}
println("result = $result")
Copy the code
The output is:
result = null
Copy the code
3.8. Repeat () function
First, we can see from the name of this function is related to the repetition of a function, then look at the source code, from the implementation of the source code to illustrate the function:
public inline fun repeat(times: Int, action: (Int) -> Unit) {
contract { callsInPlace(action) }
for (index in 0..times - 1) {
action(index)
}
}
Copy the code
We can see from the above code that this function is used to repeat the desired action based on the number of iterations passed in.
Ex. :
repeat(5){ println("I am the first to repeat${it + 1}Times, my index is:$it")}
Copy the code
The output is:
I am repeating the first time, my index is: 0
I repeat it twice and my index is: 1
I repeat it for the third time and my index is: 2
I am repeating for the fourth time and my index is: 3
I am repeating for the fifth time and my index is: 4
Copy the code
3.9. Lazy ()
The Lazy() function implements four overloaded functions, all of which are used to delay operations, but I won’t cover that here. This is often used in real project development to delay initialization of properties. I’ve already explained this in variables and constants. No more introductions here…
Fourth, the summary of the standard higher-order functions
Generally, there are only three functions: T.also, T. et and T. pply. And these three functions are all examples of the differences between these functions. Therefore, detailed examples are not introduced here. And use these higher-order functions coherently to deal with certain logic, which is rarely done in real projects. Generally, one is used alone, or two or three are used together. But after mastering these functions, I’m sure you can too. Here due to space reasons do not do examples to explain..
About the differences between them, and how they are used in actual projects under certain requirements to choose which functions to use, I hope you take a detailed look at their source code and analyze according to the examples I wrote earlier.
The Kotlin standard functions: Run, with, let, Also and Apply The Kotlin standard functions that we didn’t understand in those years
conclusion
Now that we’ve chosen Kotlin, the programming language. That the higher-order function must master a knowledge point, because, in the source code of the system, the implementation of a large number of higher-order function operations, in addition to the above explained to the standard higher-order function, for String (String) and set, etc., are using higher-order functions to write some of their common operations. For example, element filtering, sorting, fetching, grouping, and so on, it is important to practice the standard higher-order functions described above, because they can really save a lot of code in actual project development
This article has been collected on GitHub: Jetictors/KotlinLearn, welcome star articles continue to update, can be wechat search “J guy talk” first time read, everyone’s three-in-one is the best power for the old J, you are sure not to wave? If you have any mistakes or suggestions on this blog, please leave comments