Kolin笔记08(高阶函数,内联函数)

  1. 1. 高阶函数
    1. 1.1. 基本规则
    2. 1.2. 使用
  2. 2. 内联函数: inline
  3. 3. noinline与crossinline

高阶函数

定义:如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,该函数称为高阶函数。

Kotlin增加了一个函数类型概念,也就是说可以传递函数作为参数

基本规则

1
2
//基本规则
(String, Int) -> Unit

解释: -> 左边部分用来声明该函数接收什么参数,多个参数之间用逗号隔开。 右边部分用于声明该函数返回值是什么类型,如果没有就是 Unit 相当于java中的Void

1
2
3
fun example(block : (String, Int) -> Unit){
block("Hello",123)
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun num1AndNum2(num1:Int,num2:Int, operation: (Int,Int)->Int):Int{
val result = operation(num1,num2)
return result
}
//声明两个方法,加法和减法
fun plus(num1: Int,num2: Int):Int{
return num1 + num2
}
fun minus(num1: Int,num2: Int):Int{
return num1 - num2
}
fun main(){
val num1 = 100
val num2 = 80
val result1 = num1AndNum2(num1,num2, ::plus)
val result2 = num1AndNum2(num1,num2,::minus)
println("result1 is ${result1}")
println("result2 is ${result2}")
}

这里 ::plus ,::minus 的写法是函数的引用写法,表示将plus(),minus()函数作为参数传递给num1AndNum2()函数中。

简化:显然不可能每次使用高阶函数都要创建新的函数,这样太麻烦了。因此可以这么写。

1
2
3
4
5
6
7
8
9
10
11
12
fun main(){
val num1 = 100
val num2 = 80
val result1 = num1AndNum2(num1,num2){ n1,n2 ->
n1 + n2
}
val result2 = num1AndNum2(num1,num2){ n1,n2 ->
n1 - n2
}
println("result1 is ${result1}")
println("result2 is ${result2}")
}

内联函数: inline

简单分析下高阶函数的实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//Kotlin会把num1AndNum2高阶函数,大致转换为java的这种写法
public static int num1AndNum2(int num1,int num2,Function operation){
int result = (int)operation.invoke(num1,num2);
return result;
}
public static void main(){
int num1 = 100;
int num2 = 80;
int result = num1AndNum2(num1,num2,new Function{
@Override
public Integer invoke(Integer num1,Integer num2){
return num1 + num2;
}
})
}
/*
Kotlin将第三个参数转换为了一个Function接口,Kotlin内置的一个接口,Lambda表达式变成了匿名类的实现方式,在invoke当中实现了n1 + n2的逻辑,最后返回。
Lambda表达式每次调用都会创建一个新的匿名类,因此会造成额外的内存和性能的开销。
可以利用内联函数 inline 将Lambda表达式带来的运行开销完全消除
*/

用法:

1
2
3
4
5
6
7
//非常简单只需要在定义高阶函数时加上inline关键字即可
//内联函数 inline,以num1AndNum2函数为例,直接加一个inline即可.减小了内存开销
//原理,Kotlin编译器会将Lambda表达式代码替换到调用的地方,然后再将内联函数的全部代码替换到调用的地方
inline fun num1AndNum23(num1:Int,num2:Int, operation: (Int,Int)->Int):Int{
val result = operation(num1,num2)
return result
}

noinline与crossinline

前面说明了,inline是直接将Lambda表达式替换到了调用的地方,减小了内存开销。考虑一个情形:如果高阶函数接收了两个或更多的函数类型参数,此时加上了inline关键字,Kotlin编译器会自动将所有的引用全部进行内联。如果我们只想让它内联其中一个Lambda表达式怎么办呢?

这时就可以考虑使用noinline:

1
inline fun inlineTest(block1 : () -> Unit, noinline block2: () -> Unit){}

block2 参数前加上了noinline关键字,现在就对block1进行内联,block2就不会了。

为什么Kotlin要提供noinline关键字

由于内联函数需要进行代码替换,因此它不是真正的参数属性,非内联函数类型参数可以自由的传递给其他任何函数,因为他是一个真实的参数,而内联函数,只能传给另一个内联函数。

绝大多数高阶函数是可以直接声明成内联函数的,但也有少部分情况。

1
2
3
4
5
inline fun runRunnable(block : () -> Unit){
val runable = Runnable{
block()
}
}

这里会进行报错,首先,在runRunnable函数中,创建了一个Runnable对象,在Runnable的Lambda表达式当红传输了函数类型参数。而Lambda表达式在编译的时候会被转成匿名类的实现方式,实际上上述的代码是在匿名类当中调用了传入的函数类型参数。

内联函数中引用 Lambda表达式允许用return关键字进行返回,但是由于在匿名类中调用的函数类型参数,此时不能进行外层调用函数返回的,只能对匿名类中的函数调用返回。:dizzy_face::dizzy_face::dizzy_face:

此时就可以用crossinline关键字

1
2
3
4
5
inline fun runRunnable(crossinline block : () -> Unit){
val runable = Runnable{
block()
}
}

加上了crossinline就不会报错了,加上crossinline后,无法再调用runRunnable函数时的lambda表达式中用return进行返回了,但是可以使用 return@runRunnable进行局部返回。