Higher order functions
An important characteristic of a higher-order function is that its parameter type contains a function, or that its return value type is a function type.
Here’s how to declare a function as an argument to another function:
Funparamfunction (block () -> Unit){block()} funParamFunction (block () -> Unit){block()}Copy the code
The return value is the form of a function
fun returnFunction():() -> Long{
return {System.currentTimeMills()}
}
Copy the code
Calls to higher-order functions
An important feature of higher-order functions is that function type arguments can determine the execution logic of the function. That is, functions with the same return type but different execution logic can return different results when passed as arguments. Let’s define a function:
Function arguments block are two function variables that take ints and return values Int fun exampleFun(a: Int, b: Int, block: (Int, Int) -> Int): Int { return block(a, Fun aFun(n:Int,m:Int):Int{return m*n} fun bFun(n:Int,m:Int):Int{return m+n} // call fun Main () {val aValue = exampleFun(2,3,::aFun) println(" return value of aFun as parameter: $aValue") val bValue = exampleFun(2,3,::bFun) println(" return value of bFun as a parameter: $bValue")} // return result aFun as a parameter: 6 bFun as a parameter: 5Copy the code
The point here is that we need to define multiple functions with different logic, which is relatively troublesome, but in fact, we can see that the function parameters passed can be simplified into lamba expressions. We learned lamba expressions before, we can know:
val func:(Int) -> Unit = {p:Int -> println(p) }
The lamba expression for the above block is:
Val block:(Int, Int) -> Int = {a:Int,b:Int -> a+b}
Where a+b is the execution logic, it can be a+b, a-b, or a*b, as long as it returns an Int.
The previous high-level function calls can be written flexibly
Val aValue = exampleFun(2,3){a,b -> a+b}Copy the code
2. Inline functions
An inline function is preceded by the inline keyword modifier. Inline function can reduce the use of function calls, there is a performance of overhead is optimized, higher-order functions and and inline here will be more match, the reason is that call higher-order functions, as a function parameter of the variable call will also be a function call, this time will create an anonymous class objects instead of Lamba expression, the more object creation, Memory overhead is bound to be high. Take a look at the code below
fun inlineFun(block:() -> Unit){ val curTime = System.currentTimeMillis() block() println(System.currentTimeMillis() - InlineFun {println("Kotlin")} // We just want to call the print method to see when it is called, but we still create an anonymous class for the lamba expression. Inline fun inlineFun(block () -> Unit){val curTime = System.currenttimemillis () block() println(system.currentTimemillis () -curtime)} Var curTime = system.currentTimemillis () println("Kotlin")} println(System.currentTimeMillis() - curTime)Copy the code
This removes the overhead of creating lamba expression anonymous classes at compile time, so the principle behind inline functions is that Kotlin moves the code inside the inline function to where it is called at compile time, just as if we had written the code.
Inline functions of noinline and Crossinline
If a higher-order function is modified with the inline keyword, all parameters of the function type it receives are inline. If a function type is not to be inline, the noinline keyword is used.
This raises a question: since we said that inline functions eliminate the extra memory overhead associated with running Lambda expressions, why is noinline provided to exclude inlining?
This is mainly because arguments of an inline function type can only be passed to an inline function, whereas non-inline functions can be passed to any function. The other difference is that an inline function can return an external function, that is, the function that called the inline function, whereas a non-inline function returns a local function.
The following code looks at the return from an inline function and the return from a non-inline function:
inline fun nonLocalReturn(block:() -> Unit){ block() } fun main(){ println("main start") nonLocalReturn{ println("lambda Return println("lambda end")} println("main end")} main start lambda Start means that the function is returned directly to main, Fun nonLocalReturn(block () -> Unit){block()} fun main(){println("main start") NonLocalReturn {println("lambda start") return@nonLocalReturn println("lambda end")} println("main end")} // The result printed here is Lambda start main end indicates that only nonLocalReturn is returned, and main still goes downCopy the code
Crossinline keyword
Most higher-order functions can be declared inline, but there are exceptions. Such as the following code:
inline fun Runnable(block:() -> Unit):Runnable{
return object:Runnbale {
override fun run(){
block()
}
}
}
Copy the code
In this case, IDEA will prompt a block() call error. This is because there may be illegal non-local return, and the calling context of block() is not the same as the defined one.
So how do you solve this problem?
Using the crossinline keyword to modify a passed function ensures that it does not return in a Lambda expression, and crossinline decorates the same properties of all inline functions except that it does not return.
Three, several commonly used higher-order functions
Let the function
Let function calls are typically for a variable defined to be used in a particular situation, for example we can avoid null judgments on variables. Observe the following code:
// let is called when any is not null. .let {//it represents any object. // todo() is the method of any object.Copy the code
The actual scenario in Android development is that we may have to nullify a variable multiple times before calling it, as in:
mTextView? .text = "Kotlin" mTextView? .textSize = 16fCopy the code
Using the let function, it can be abbreviated as
mTextView? .let{ it.text = "Kotlin" it.textSize = 16f }Copy the code
The run function
The run function takes a lambda function as an argument, passes this to it, and returns the result as a closure. The code above can be optimized as follows:
mTextView? .run{ text = "Kotlin" textSize = 16f }Copy the code
The apply function
The structure of Apply is similar to that of run, except that apply returns the calling object itself, which makes it particularly useful for multiple levels of null-detection. Let’s take a look at a code example:
The general structure of the apply function:
Var applyRes = any.apply{//todo() is a common property or method of any. Todo () returns any instead of 2, 1+1}Copy the code
A simple example of applying Apply:
Class Company(var department: department? = null){ class Department(var departmentName: String? = null,var person:Person? = null){ class Person(var name:String? = null) } } fun main() { val company:Company? = Company (Company. Department (" a ", the Company. The Department. The Person (" Jeremy "))) Company? .department?. Apply {departmentName = "departmentName"}?.person?.name. Println (" ${company?.department?. DepartmentName} employee is $it")}} The employee is JeremyCopy the code
The braking function
In the above apply application example, we finally called also function, which is similar to let function, where it is the returned call object itself, and its general structure format is as follows:
Val alsoRes = any.also {//it represents any. Todo is an attribute of any. Todo () 1+1 returns any, not 2}Copy the code
The use function
One of the biggest features of the use function is that it automatically closes the caller, regardless of whether there is an exception. Because use is also implemented internally ina try-catch-finally block, and the close operation is performed in finally, so whether it ends normally or an exception occurs, All close the caller correctly.
How do I call the use function?
The use function can be called from objects that implement the Closeable interface.
This is because the use function makes manipulation of File objects and IO streams in Kotlin streamlined.
File("build.gradle").inputStream().reader().buffered()
.use {
print(it.readLines())
}
Copy the code
Fourth, set transformation sequence
4.1. Filter Operation
The operation of filter is to retain the elements that meet the criteria to get a new list, observe the following code:
Val arr = intArrayOf(1,2,3,4) //asSequence() converts to lazy sequence val arrFilter = arr.assequence ().filter {it%2 == 0} val arrFilter = Arr. Filter {it%2 == 0} for (e in arrFilter){println(e)} Print data: 2 4Copy the code
4.2 map transformation
A map transformation is a one-to-one mapping operation:
val list: MutableList<Int> = mutableListOf(1, 2, 3, 4) val listMap = list.map {it*2 + 1} for (e in listMap){println(e)} Each data in the 3, 5, 7, 9 set returns a new data set corresponding to it*2 + 1 in the LAMBA expressionCopy the code
Let’s look at the combination of filter and map
Lazy sequence call method:
val list: MutableList<Int> = mutableListOf(1, 2, 3, 4) list.asSequence().filter { println("filter $it") it % 2 ==0 }.map { println("map $it") it*2 + 1 }.forEach { Println ("forEach $it")} The following output is displayed: filter 1 filter 2 map 2 forEach 5 filter 3 filter 4 map 4 forEach 9Copy the code
ForEach is a valve. If you remove forEach, the previous filter and map operations will not be performed. This is the meaning of the lazy sequence, which will be executed when you need it. Let’s look at the result of a hungry call:
val list: MutableList<Int> = mutableListOf(1, 2, 3, 4) list.filter { println("filter $it") it % 2 ==0 }.map { println("map $it") it*2 + 1 }.forEach { println("forEach $it") } Filter 1 filter 2 filter 3 Filter 4 map 2 map 4 forEach 5 forEach 9Copy the code
You can see that whether it meets the condition or not, it will execute first before proceeding to the next step.
4.3 flatMap transformation
FlatMap is to map the elements of a set to a set, and each element corresponds to a new set. Finally, these sets are formed into a final new set. Such as the following code:
val list: MutableList<Int> = mutableListOf(1, 2, 3, 4) list.flatMap {0 until it}.joinToString(). Let (::println) Print the result 0, 0, 1, 0, 1,2, 0,1,2,3 is the corresponding set of each element: 0 -> 0 1 -> 0,1 2 -> 0,1,2 3 -> 0,1,2,3Copy the code
FlatMap returns only Iterable, as follows:
val list: MutableList<Int> = mutableListOf(1, 2, 3, 3) Val listFlatMap = list.flatMap {listOf(it+1)} for (e in listFlatMap){println(e)Copy the code
4.4 Examples of aggregation operations for collections
Sum: Sum of all elements.
Reduce: The elements are aggregated in order and the result is consistent with the element type.
Fold: Given an initialization value, elements are aggregated in a regular manner, resulting in the same type of initialization value.
The reduce function is a fold function.
val list: MutableList<Int> = mutableListOf(1, 2, 3, 4) val strFold = list.fold(StringBuffer()){ I -> acc.append(I)} println(strFoldCopy the code