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