Kotlin: high order function, small code farmers also have big dreams

1.1. Higher order function when function is used as function parameter

Here we introduce sumBy{} higher-order functions in strings. Take a look at the source code first

//  Source code of sumBy function  
public inline fun CharSequence.sumBy(selector: (Char) -> Int): Int {  
    var sum: Int = 0  
    for (element in this) {  
        sum += selector(element)  
    }  
    return sum  
}  

Source code Description:

  1. You don't have to worry about inline and CharSequence in front of sumBy function. Because this is the inline function and extension function in Koltin. It will be explained to you in the following chapters. Here we mainly analyze higher-order functions, so we don't do more analysis here.
  2. This function returns a value of type Int. And accepts a selector() function as an argument to the function. The selector() function accepts a parameter of type Char and returns a value of type Int.
  3. Define a sum variable and loop the string, calling the selector() function once and adding sum. Used as accumulation. Where this keyword represents the string itself.

Therefore, this function is used to convert each character in the string into the value of Int for accumulation, and finally return the accumulated value

Example:

val testStr = "abc"  
val sum = testStr.sumBy { it.toInt() }  
println(sum)  

The output result is:

294  //  Because the value of character a corresponds to 97,b corresponds to 98 and c corresponds to 99, the value is   ninety-seven  +  ninety-eight  +  ninety-nine  =  two hundred and ninety-four  

1.2. A higher-order function that uses a function as the return value of a function

Here we use an example on the official website to explain. lock() function, first take a look at its source implementation

fun <T> lock(lock: Lock, body: () -> T): T {  
    lock.lock()  
    try {  
        return body()  
    }  
    finally {  
        lock.unlock()  
    }  
}  

Source code Description:

  1. This uses the knowledge of generics in kotlin, which is not considered here. I will explain it to you in the following articles.
  2. As can be seen from the source code, the function accepts a variable of Lock type as parameter 1 and a function with no parameter and return type T as parameter 2
  3. The return value of this function is a function. We can see from the code return body().

Example: using the lock function, the following codes are pseudo codes. I took them directly according to the example on the official website

fun toBeSynchronized() = sharedResource.operation()  
val result = lock(lock, ::toBeSynchronized) 

Among them,:: toBeSynchronized refers to the reference to the function toBeSynchronized(). The use of double colon:: is not discussed and explained here.

The above can also be written:

val result = lock(lock, {sharedResource.operation()} )  

1.3 use of higher-order functions

In the above two examples, we have the expression str.sumby {it. Toint}. In fact, this way of writing has been explained in the previous chapter on the use of Lambda. Here we mainly talk about the abbreviation of Lambda syntax in higher-order functions.

From the above example, we should write as follows:

str.sumBy( { it.toInt } )  

However, according to the Convention in Kotlin, when there is only one function in the function as an argument and you use a lambda expression as the corresponding argument, you can omit the parentheses () of the function. Therefore, we can write:

str.sumBy{ it.toInt }  

Another convention is that when the last parameter of a function is a function and you pass a lambda expression as the corresponding parameter, you can specify it outside the parentheses. Therefore, the code in example 2 above can be written as:

val result = lock(lock){  
    sharedResource.operation()  
}  

2, Custom higher order functions

Let's look at an example first:

//  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  

It can be seen that in the above code, the value is written directly in my method body, which is very unreasonable in development and will not be written like this. The above example is just explaining the syntax of Lambda. Let me give another example:

Example: pass in two parameters 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() {  
    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")  
}  

The output result is:

result1 = 3  
result2 = -1  
result3 = 30  
result4 = 2 

This example implements the +, -, *, /, of two numbers according to the different Lambda expressions passed in.

3, Introduction to common standard higher-order functions

Several standard higher-order functions commonly used in Kotlin are introduced below. Skilled use of the following functions can reduce a lot of code and increase the readability of the code. The source codes of the following high-order functions are almost all from the Standard.kt file

3.1 TODO function

This function is not a high-order function, but an ordinary function that throws exceptions and tests errors.

This function displays a NotImplementedError Error thrown. The NotImplementedError Error class inherits from Error in Java. We can see from his source code:

public class NotImplementedError(message: String = "An operation is not implemented.") : Error(message)  

TODO function source code

@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")  

For example:

fun main(args: Array<String>) {  
    TODO("test TODO Function to display whether to throw an error")  
}  

The output result is:

[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-pp0jidrm-1630580760478)( https://user-gold-cdn.xitu.io/2018/4/7/162a092be153ea65? imageView2/0/w/1280/h/960/ignore-error/1)]

If TODO() is called without passing parameters, An operation is not implemented

3.2. run() function

The run function is divided into two cases here, because it is also divided into two functions in the source code. Using different run functions will have different effects.

3.2.1,run()

Let's look at its source code:

public inline fun <R> run(block: () -> R): R {  
    contract {  
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)  
    }  
    return block()  
}  

Xiaosheng doesn't quite understand the meaning of the contract code. On some Daniel's blog s, it is said that its editor infers the context. But I don't know if it's right, because it's not explained on the official website. But this word means contract, contract and so on. I think it has something to do with this. I won't delve into it here. It mainly talks about the usage and meaning of run {} function.

Here, we only care about the line of return block(). From the source code, we can see that the run function only executes our block (), that is, a Lambda expression, and then returns the execution result.

Usage 1:

This function can be used when we need to execute a code block, and the code block is independent. That is, I can write some project independent code in the run() function, because it will not affect the normal operation of the project.

Example: use in a function

private fun testRun1() {  
    val str = "kotlin"  
  
    run{  
        val str = "java"   //  It will not conflict with the above variables  
        println("str = $str")  
    }  
  
    println("str = $str")  
}  

Output results:

str = java  
str = kotlin  

Usage 2:

Because the run function executes the lambda expression I passed in and returns the execution result, when a business logic needs to execute the same piece of code and judge different results according to 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")  

The output result is:

num = 10  

3.2.2,T.run()

In fact, the T.run() function is similar to the run() function. We can see the difference between the two by looking at their source code implementation:

public inline fun <T, R> T.run(block: T.() -> R): R {  
    contract {  
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)  
    }  
    return block()  
}  

From the source code, we can see that the function parameter block() is a function extended under type T. This shows that my block() function can use the context of the current object. So we can use this function when the lambda expression we pass in wants to use the context of the current object.

Example:

val str = "kotlin"  
str.run {  
    println( "length = ${this.length}" )  
    println( "first = ${first()}")  
    println( "last = ${last()}" )  
}  

The output result is:

length = 6  
first = k  
last = n  

Here, you can use the this keyword, because here it is the object of code str, which can also be omitted. Because we can see from the source code that block() is an extension function of type T.

In the actual development, we can use:

Example: set properties for TextView.

val mTvBtn = findViewById<TextView>(R.id.text)  
mTvBtn.run{  
    text = "kotlin"  
    textSize = 13f  
    ...  
}  

3.3. with() function

In fact, the functions of the with() function and the T.run() function are the same. Let's take a look at the implementation source code:

public inline fun <T, R> with(receiver: T, block: T.() -> R): R {  
    contract {  
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)  
    }  
    return receiver.block()  
    }  

The difference between the two functions is:

  1. with is a normal higher-order function, and T.run() is an extended higher-order function.
  2. The return value of the with function specifies the receiver as the receiver.

Example: implement the columns of the T.run() function above

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

The output result is:

length = 6  
first = k  
last = n  

Example: when my object is null able, look at the convenience between the two functions

val newStr : String? = "kotlin"  
  
with(newStr){  
    println( "length = ${this?.length}" )  
    println( "first = ${this?.first()}")  
    println( "last = ${this?.last()}" )  
}  
  
newStr?.run {  
    println( "length = $length" )  
    println( "first = ${first()}")  
    println( "last = ${last()}" )  
}  

From the above code, we can see that when we use null able objects, using T.run() is better than using the with() function in terms of code readability and simplicity. Of course, how to choose to use these two functions depends on the actual needs and your preferences.

3.4. T.apply() function

Let's first look at the source code of the T.apply() function:

public inline fun <T> T.apply(block: T.() -> Unit): T {  
    contract {  
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)  
    }  
    block()  
    return this  
}  

From the T.apply() source code, combined with the T.run() function source code mentioned earlier, we can conclude that the logic of the two functions is similar. The only difference is that T. apply returns its own object after executing the block() function. T.run returns the result of execution.

Therefore, the function of T.apply can not only realize the function of T.run, but also carry out subsequent operations. Let's look at an example

Example: after setting properties for TextView, set click events, etc

val mTvBtn = findViewById<TextView>(R.id.text)  
mTvBtn.apply{  
    text = "kotlin"  
    textSize = 13f  
    ...  
}.apply{  
    //  Here you can continue to set properties or other operations of TextView  
}.apply{  
    setOnClickListener{ .... }  
}  

Or: set to Fragment to set data transfer

//  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  
}  
  
//  Improvement 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)  
}  

3.5. T.also() function

For the T.also function, it is very similar to T.apply,. Let's first 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  
}  

From the above source code combined with the source code of T.apply function, we can see that the parameter block function in T.also function passes in its own object. Therefore, this function is used to call its own object with the block function, and finally return its own object

Here is a simple example to illustrate the difference between it and T.apply

Example:

"kotlin".also {  
    println("result: ${it.plus("-java")}")  
}.also {  
    println("result: ${it.plus("-php")}")  
}  
  
"kotlin".apply {  
    println("result: ${this.plus("-java")}")  
}.apply {  
    println("result: ${this.plus("-php")}")  
}  

Their output is the same:

result: kotlin-java  
result: kotlin-php  
  
result: kotlin-java  
result: kotlin-php  

From the above examples, we can see that the difference between them is that T.also can only use it to call itself, while t.apply can only use this to call itself. Because in the source code, T.also returns itself after executing block(this). T.apply returns itself after executing block(). This is the key to why it can be used in some functions and only this can be used in some functions

summary

In fact, it's easy to master it. There are two main points:

  1. Find a set of good video materials and follow Daniel's sorted knowledge framework for learning.
  2. Practice more( The advantage of video is that it has a strong sense of interaction and is easy to concentrate)

You don't need to be a genius or have a strong talent. As long as you do these two points, the probability of success in the short term is very high.

For many junior and intermediate Android engineers, if they want to improve their skills, they often grope and grow by themselves. The learning effect of fragmentation is inefficient, long and helpless.

The above is the summary of the interview. I hope it can be of some help to you. In addition to these questions that need to be paid attention to in the interview, of course, the most important thing is to brush the questions. Here is a super comprehensive interview topic PDF I sorted out before. If you are interested, you can get it by yourself or send a private letter to me:

also   Advanced architecture technology advanced brain map, Android development interview special materials, advanced architecture materials help you learn and improve the advanced level, and also save you time to search for materials on the Internet. You can also share them with your friends to learn together.

CodeChina open source project: Android learning notes summary + mobile architecture Video + big factory interview real questions + project actual combat source code

[PDF document of Android core advanced technology, analysis of real interview questions of BAT manufacturers]

Learn on the shelf.
2. Practice more( The advantage of video is that it has a strong sense of interaction and is easy to concentrate)

You don't need to be a genius or have a strong talent. As long as you do these two points, the probability of success in the short term is very high.

For many junior and intermediate Android engineers, if they want to improve their skills, they often grope and grow by themselves. The learning effect of fragmentation is inefficient, long and helpless.

The above is the summary of the interview. I hope it can be of some help to you. In addition to these questions that need to be paid attention to in the interview, of course, the most important thing is to brush the questions. Here is a super comprehensive interview topic PDF I sorted out before. If you are interested, you can get it by yourself or send a private letter to me:

also   Advanced architecture technology advanced brain map, Android development interview special materials, advanced architecture materials help you learn and improve the advanced level, and also save you time to search for materials on the Internet. You can also share them with your friends to learn together.

CodeChina open source project: Android learning notes summary + mobile architecture Video + big factory interview real questions + project actual combat source code

[PDF document of Android core advanced technology, analysis of real interview questions of BAT manufacturers]

[external chain picture transferring... (img-kTV0n3X9-1630580760480)]

Here are just some interview questions sorted out, which will be continuously updated in the future. I hope these advanced interview questions can reduce the threshold of interviewing Android posts, so that more Android engineers can understand and master the Android system. If you like, please click one to pay attention~

Keywords: Java Android Design Pattern kotlin

Added by shneoh on Fri, 03 Sep 2021 01:44:50 +0300