3.Kotlin高阶函数

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
}


  1. 定义高阶函数完整的语法规则:在函数类型的前面加上ClassName,就表示这个函数类型是定义在那个类中的。
  2. 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){
}

内联函数与非内联函数的区别:

  1. 内联的函数类型参数在编译时会被进行代码替换,因此没有真正的参数属性,非内联的函数类型可以自由地传递给其他任何函数,因此他就是一个真实的参数,而内联的函数类型参数只允许传递给另外一个内联函数。
  2. 内联函数引用的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)

例子:

  1. 定义cv方法
fun cvOf(vararg pairs:Pair<String,Any?>):ContentValues{

}
//方法作用:构建一个ContentValues对象
1. Pair类型:使用A to B 语结构创建出来的参数类型,键值对的数据结构,
      需要通过泛型指定它的键和值分别对应什么类型的数据
2. vararg:对应的是Java中的可变参数列表,可以传0个,1个,2个甚至多个Pair类型的参数          
  1. 实现逻辑功能:
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
}
  1. 使用
val values = cvOf("name" to "Game of Thrones","author" to "George Martin",
                 "pages" to 720,"price" to 20.85)
db.insert("Book",null,values)
  1. 结合高阶函数进行进一步优化
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"){
	//处理具体逻辑
}

特点:

  1. 不能定义成顶层函数
  2. 必须是某个类的成员函数
  3. 可以使用扩展函数的方式将它定义到某个类中
  4. 只能接收一个参数

例子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)
  1. 将to()函数定义在了A类型下,并接收一个B类型的参数,A和B可以是两种不同类型的泛型,使得我们可以构建出字符串to整型的键值对
  2. 创建并返回了一个Pair对象,A to B 实际上得到的是一个包含A,B数据的Pair对象。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值