文章目录
1.概述
高阶函数——一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,这个函数是高阶函数。
作用:允许让函数类型的参数来决定函数的执行逻辑
例子:
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)
//函数引用方式的写法,将plus()和minus()函数作为参数传递给num1AndNum2()函数
println("result1 is $result1")
println("result2 is $result2")
}
1.1 Lambda表达式
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}
//当Lambda参数是函数的最后一个参数时,将Lambda表达式移到函数括号的外面
println("result1 is $result1")
println("result2 is $result2")
}
2.拓展
fun StringBuilder.build(block:StringBuilder.() -> Unit):StringBuilder{
block()
return this
}
- 定义高阶函数完整的语法规则:在函数类型的前面加上ClassName,就表示这个函数类型是定义在那个类中的。
- Lambda表达式在底层被转换成了匿名类的实现方式,每调用一次Lambda表达式,都会创建一个新的匿名类实例,会造成额外的内存和性能开销。
2.内联函数——inline
在定义高阶函数时加上inline关键字,Kotlin编译器会将内联函数中的代码在编译时自动替换到调用它的地方,就不存在运行时的开销了。
1. 首先Kotlin编译器会将Lambda表达式中的代码替换到函数类型参数调用的地方
2. 再将内联函数中的全部代码替换到函数调用的地方
所以,内联函数能完全消除Lambda表达式带来的运行时开销
3.noinline与crossinline
加上inline关键字,Kotlin编译器会自动将所有引用的Lambda表达式全部进行内联
只想内联一个Lambda表达式
1.
inline fun inlineTest(block1:() -> Unit, noinline block2:() -> Unit){
}
内联函数与非内联函数的区别:
- 内联的函数类型参数在编译时会被进行代码替换,因此没有真正的参数属性,非内联的函数类型可以自由地传递给其他任何函数,因此他就是一个真实的参数,而内联的函数类型参数只允许传递给另外一个内联函数。
- 内联函数引用的Lambda表达式可以使用return关键字进行函数返回;非内联函数只能进行局部返回
例子:
fun printString(str:String,block:(String) -> Unit){
println("printString begin")
block(str)
println("printString end")
}
fun main(){
println("main start")
val str = ""
printString(str){
s->
println("lambda start")
if(s.isEmpty()) return@printString
println(s)
println("lambda end")
}
println("main end")
}
return@printString之后的语句没有打印
inline fun printString(str:String,block:(String) -> Unit){
println("printString begin")
block(str)
println("printString end")
}
fun main(){
println("main start")
val str = ""
printString(str){
s->
println("lambda start")
if(s.isEmpty()) return
println(s)
println("lambda end")
}
println("main end")
}
3.绝大部分高阶函数可以直接声明成内联函数,以下是例外
inline fun runRunnable(block:() -> Unit){
val runnable = Runnable{
block()
}
runnable.run()
}
这样写会报错:
在runRunnable()函数中,创建了一个Runnable对象,在Runnable的Lambda表达式中调用了传入的函数类型参数
Lambda表达式在编译时会被转换成匿名类的实现方式——在匿名类中调用了传入的函数类型参数;
Lambda表达式允许使用return关键字进行函数返回,但是在匿名类中调用了传入的函数类型参数,不可能进行外层调用函数返回,最多只能对匿名类中的函数调用进行返回,这里出错。
所以:在高阶函数中创建了另外的Lambda或匿名类的实现,在这些实现中调用函数类型参数,再将高阶函数声明成内联函数,一定会提示错误
4. 借助crossinline解决
inline fun runRunnable(crossinline block:() -> Unit){
val runnable = Runnable{
block()
}
runnable.run()
}
crossinline,用于保证在内联函数的Lambda表达式中一定不会使用return关键字
4.高阶函数的应用
适用于简化各种API的调用
4.1 简化SharedPreferences的用法
原来的用法:
val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
editor.putString("name","Tom")
editor.putInt("age",28)
editor.putBoolean("married",false)
editor.apply()
简化之后:
fun SharedPreferences.open(block: SharedPreferences.Editor.() ->Unit){
val editor = edit()
editor.block()
editor.apply()
}
1.通过扩展函数的方式向SharedPreferences类中添加了一个open函数,
并且还接收一个函数类型的参数,所以open是一个高阶函数
2.接收的是一个 SharedPreferences.Editor.()的函数类型参数,
这里需要调用editor.block()对函数类型参数进行调用
这样就可以在函数类型参数的具体实现中添加数据
3.最后调用editor.apply()方法提交数据
使用:
getSharedPreferences("data",Context.MODE_PRIVATE).open {
putString("name","Tom")
putInt("age",28)
putBoolean("married",false)
}
//注意:现在Lambda表达式拥有的是SharedPreferences.Editor的上下文环境
4.2 简化ContentValues的用法
ContentValues主要用于结合SQLiteDatabase的API存储和修改数据库中的数据:
val values= ContentValues()
values.put("name","Game of Thrones")
values.put("author","George Martin")
values.put("pages","720")
values.put("Book",null,values)
例子:
- 定义cv方法
fun cvOf(vararg pairs:Pair<String,Any?>):ContentValues{
}
//方法作用:构建一个ContentValues对象
1. Pair类型:使用A to B 语结构创建出来的参数类型,键值对的数据结构,
需要通过泛型指定它的键和值分别对应什么类型的数据
2. vararg:对应的是Java中的可变参数列表,可以传0个,1个,2个甚至多个Pair类型的参数
- 实现逻辑功能:
fun cvOf(vararg pairs:Pair<String,Any?>):ContentValues{
val cv = ContentValues()
for(pair in pairs){
val key = pair.first
val value = pair.second
when(value){
is Int -> cv.put(key,value)
is Long -> cv.put(key,value)
is Short -> cv.put(key,value)
is Float -> cv.put(key,value)
is Double -> cv.put(key,value)
is Boolean -> cv.put(key,value)
is String -> cv.put(key,value)
is Byte -> cv.put(key,value)
is ByteArray -> cv.put(key,value)
null -> cv.putNull(key)
}
}
return cv
}
- 使用
val values = cvOf("name" to "Game of Thrones","author" to "George Martin",
"pages" to 720,"price" to 20.85)
db.insert("Book",null,values)
- 结合高阶函数进行进一步优化
fun cvOf(vararg pairs:Pair<String,Any?>) = ContentValues().apply{
//apply函数返回它的调用对象本身
for(pair in pairs){
val key = pair.first
val value = pair.second
when(value){
is Int -> put(key,value)
is Long -> put(key,value)
is Short -> put(key,value)
is Float -> put(key,value)
is Double -> put(key,value)
is Boolean -> put(key,value)
is String -> put(key,value)
is Byte -> put(key,value)
is ByteArray -> put(key,value)
null -> putNull(key)
//Lambda表达式中会自动拥有ContentValues的上下文,这里直接调用各种put方法
}
}
}
5. infix函数
to并不是Kotlin语言中的一个关键字,使用A to B的语法结构是因为Kotlin提供了一种高级语法糖特性:infix函数
例子1:startsWith函数
if("Hello Kotlin".startsWith("Hello")){
//处理具体逻辑
}
//新建一个infix.kt
infix fun String.beginsWith(prefix : String) = startsWith(prefix)
//调用
if("Hello Kotlin" beginsWith "Hello"){
//处理具体逻辑
}
特点:
- 不能定义成顶层函数
- 必须是某个类的成员函数
- 可以使用扩展函数的方式将它定义到某个类中
- 只能接收一个参数
例子2:集合
val list = listOf("Apple","Banana","Orange","Pear","Grape")
if(list.contains("Banana")){
//处理具体逻辑
}
//在infix.kt文件中添加
infix fun <T> Collection<T>.has(element:T) = contains(element)
//使用
if(list has "Banana"){
//处理具体逻辑
}
to()函数的源码
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
- 将to()函数定义在了A类型下,并接收一个B类型的参数,A和B可以是两种不同类型的泛型,使得我们可以构建出字符串to整型的键值对
- 创建并返回了一个Pair对象,A to B 实际上得到的是一个包含A,B数据的Pair对象。