In Kotlin, higher-order functions are those that use a function as an argument or return value to a function.

1. Higher-order functions in cases where functions are used as function arguments

Here’s the sumBy{} higher-order function in strings. Take a look at the source code:

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

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:

  1. You don’t have to worry about it hereinline, andsumByBefore the functionCharSequence.. Because this isKoltinIn theInline functionwithExtend 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.
  2. This function returns oneIntType. And accepted oneselector()Function as an argument to the function. Among them,selector()The function takes aCharType, and return oneIntType.
  3. To define asumVariable, and loop through the string, calling it onceselector()Theta plus thetasum. Used as a summation. Among themthisThe 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

1.2 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:

  1. This uses the knowledge of generics in Kotlin, which is good or not. I’ll explain that in a future post.

  2. As you can see from the source, this function takes a variable of type Lock as argument 1 and a function with no parameters and return type T as argument 2.

  3. The return value of this function is a function, as can be seen from the following code: return body()

    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() = 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:

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() }

2. Customize higher-order functions

I remember in the last chapter we wrote an example:

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)) Int) -> Int) : Int{return a + b.invoke(3,5)} // call test(10,{num1: Int, num2: Int -> num1 + num2}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

Ex. :

private fun resultByOpt(num1 : Int , num2 : Int , result : (Int ,Int) -> Int) : Int{return result(num1,num2)} private fun testDemo() {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. Of course, in the actual project development, their own to define the implementation of higher-order functions is very rare, because the system to provide us with higher-order functions have been enough. However, once we have learned the syntax of Lambda and how to define higher-order functions. When the need arises in actual development, it is not difficult for us.

4. Commonly used standard high-order functions

4.1 the TODO function

This function is not a higher-order function, it is just a normal function that throws exceptions and tests for errors.

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(): 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:

4.2 the run ()

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.

2, 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

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() {val STR = "kotlin" run{val STR = "Java" println(" STR = $STR ")} println(" STR = "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

Of course, this example has no practical significance.

4.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.

Usage:

This cannot be used as a single block of code like the run() function above.

Ex. :

val str = "kotlin"
str.run {
    println( "length = ${this.length}" )
    println( "first = ${first()}")
    println( "last = ${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

4.3 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

Here we can see that there is not much difference with the source code implementation of the t.run () function. So the difference between these two functions is:

  1. withIs a normal function of higher order,T.run()Is an extended higher-order function.
  2. withThe return value of the function is specifiedreceiverFor the receiver.

So I could use the same example of t.run () with:

Ex. :

val str = "kotlin"
with(str) {
    println( "length = ${this.length}" )
    println( "first = ${first()}")
    println( "last = ${last()}" )
}
Copy the code

The output is:

length = 6
first = k
last = n
Copy the code

4.4 T.a pply () 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. Here’s 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 {setOnClickListener{....}. Apply {setOnClickListener{....} }}Copy the code

Or: Set to Fragment to set data passing

4.5 T.a lso () 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 (" results: ${it. Plus (" - Java ")} ")}. Also {println (" results: ${it. Plus (" - PHP ")} ")} "kotlin". The apply {println (" results: ${this. Plus (" - Java ")} ")}. Apply {println (" results: ${this.plus("-php")}") }Copy the code

Their output is the same:

Result: kotlin-java result: kotlin-php result: kotlin-java result: kotlin-phpCopy 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

4.6 let function

In the previous section on null safety and nullable properties, we explained that you can use t.et () to circumvent the null pointer problem.

So today, let’s talk about his source code implementation:

public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}
Copy the code
The let function is an extension of the parameterized type T. Within the let block, it can be accessed, returning the last line of the let block, or specifying a return expression.Copy the code

The role of LET mainly lies in three points:

  • Is responsible for the last line or retuan statement of the current scope
  • Air safety detection
  • Operate on the result of the chain

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(" $it") // kotlin it. Reversed ()}.let {println(" the value of the string: $it") // niltok it.plus("-java")}. Let {println(" new string: $it") // niltok-java} "kotlin". $it") // kotlin it. Reversed ()}. Also {println(" $it") // Kotlin it. Plus ("-java")}. $it") // kotlin} "kotlin". Apply {println(" $this") // kotlin this.reversed()}. Apply {println(" $this"); $this") // kotlin this.plus("-java")}. Apply {println(" new string: $this") // kotlin}.Copy the code

Output the same result as the comment:

Original string: Kotlin reversed string value: niltok New string: niltok- Java Original string: Kotlin reversed string value: Kotlin New string: Kotlin reversed string value: Kotlin Kotlin New string: kotlinCopy the code

4.7 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 does:

To execute a desired action (function) repeatedly based on the number of iterations passed in.

Ex. :

Repeat (5){println(" ${it + 1} times, my index is: $it")}Copy the code

The output is:

I repeat the first time, my index is: 0 I repeat the second time, my index is: 1 I repeat the third time, my index is: 2 I repeat the fourth time, my index is: 3 I repeat the fifth time, my index is: 4Copy the code