单表达式函数
函数返回单个表达式时,返回值类型自动推断可以不写,可以省略 return 使用 = 赋值为表达式结果。
//返回值类型可以不写,自动推断
fun getNum(x:Int, y:Int) = x + y
fun getName(name: String) = println("名字是:$name")
//可以直接 return 表达式的结果
fun generateAnswerString(countThreshold: Int): String {
return if (count > countThreshold) {
"I have the answer."
} else {
"The answer eludes me."
}
}
//还可以将 return 关键字替换为赋值运算符
fun generateAnswerString(countThreshold: Int): String = if (count > countThreshold) {
"I have the answer"
} else {
"The answer eludes me"
}
高阶函数:
通常不会单独定义一个Lambda玩 ,而是把Lambda作为另一个函数的参数使用,传参的时候能直接写而不是麻烦地专门写个函数再去传参(注意时间顺序,成员引用是刚好已存在满足要求的函数,而不是这里说的需要了再去写)
高阶函数是指将一个函数作为另一个函数的参数或者将一个函数作为另一个函数的返回值。
与java不同的是,在Kotlin中增加了一个函数类型的概念,如果我们将这种函数添加到另一个函数的参数声明或返回值声明当中,那么这就是一个高阶函数了。
如果用f(x)、g(x)用来表示两个函数,那么高阶函数可以表示为f(g(x))
Kotlin为开发者提供了丰富的高阶函数,比如Standard.kt中的let、with、apply,run等(会在下篇文章里写出)
//函数类型block:()->Unit作为另一个函数test2()的参数
public fun test2(test:Int,block:()->Unit){
var v= block()
DTLog.i("TestTest","Test1")
}
//函数类型block:()->T作为另一个函数test22()的参数和返回值
public fun T.test22(block:()->T):T{
return block()
}
//函数类型block:T.()->Unit作为另一个函数test26()的参数
public fun T.test26(block:T.()->Unit){
block()
}
函数类型:
在Kotlin中,函数是一等公民(first class),函数是有类型的, 函数可以被存储在变量或者数据结构中,函数也可以作为变量 赋值/传参 了。Kotlin使用函数类型来描述一个函数的具体类型。一个完整语法的函数类型如下:
(x:Int, y:Int) -> Int
//函数参数的变量名可以省略不写
(Int, Int) -> Int
-> 箭头左侧的是函数的参数,描述了参数个数和类型,参数必须用小括号括起来;如果没有参数直接使用()表示就可以了;
->箭头右边表示该函数的返回值是什么类型, 注意如果函数返回值是Unit型时,不能省略不写。
函数类型可以有一个额外的接收者类型,它的语法如下:
接收者类型在参数前,和参数所在的小括号用点连接。
Int.(Int) -> Int
对比:带接收者类型的函数类型
ReceiverType 是接收者类型,指定了在 Lambda 表达式内可以在哪种接受者类型的对象上调用。this=ReceiverType
ReturnType 是 Lambda 表达式的返回类型。
receiverInstance 是接收者对象ReceiverType的实例,通过它可以在 Lambda 表达式中访问接收者类型的成员。
/**
* ReceiverType 是接收者类型,指定了在 Lambda 表达式内可以在哪种接受者类型的对象上调用。this=ReceiverType
* ReturnType 是 Lambda 表达式的返回类型。
* receiverInstance 是接收者对象ReceiverType的实例,通过它可以在 Lambda 表达式中访问接收者类型的成员。
* */
//lambda表达式
val myLambda: ReceiverType.() -> ReturnType ={
Log.e( TAG, "在 Lambda 表达式内输出接收者类型对象this=$this")
// lambda body
// 在此处可以使用 ReceiverType 的成员
this.receiverMeth()
this.foo()
val returnType:ReturnType = ReturnType()
returnType
}
val receiverInstance = ReceiverType()
val result: ReturnType = myLambda.invoke(receiverInstance)
Log.e( TAG,"输出result=$result")
/**
* 调用ReceiverType类的成员函数receiverMeth()
* 调用ReceiverType类的扩展函数ReceiverType.foo()
* 输出result=com.my.runalone_higherorderfunction.bean.ReturnType@d0e3cbb
* */
class ReceiverType {
val TAG="HigherOrderFunctionSuspendActivity3"
//定义ReceiverType类的成员函数foo()
fun receiverMeth(){
Log.e(TAG, "调用ReceiverType类的成员函数receiverMeth()")
}
//定义类型ReceiverType的扩展函数foo()
fun ReceiverType.foo(){
Log.e(TAG, "调用ReceiverType类的扩展函数ReceiverType.foo()")
}
}
class ReturnType {
}
函数类型的实例化:
⒈使用函数类型的lambda表达式的字面值和函数类型的匿名函数的字面值 进行赋值
class HigherOrderFunctionSuspendActivity3 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
//单表达式函数
fun square1(s1: Int, s2: Int):Int=s1 * s2
// 具名函数
fun square2(s1: Int, s2: Int):Int{
return s1 * s2
}
// 函数类型的匿名函数的字面值
val nimin1:(s1: Int, s2: Int)->Int =fun(s1: Int, s2: Int):Int{
return s1 * s2
}
// 函数类型的匿名函数的字面值
val nimin2:( Int, Int)->Int =fun(s1: Int, s2: Int):Int{
return s1 * s2
}
// 函数类型的匿名函数的字面值
val nimin3 = fun(s1: Int, s2: Int):Int=s1 * s2
//函数类型的lambda表达式的字面值
val squsreFun31: (s1: Int, s2: Int)->Int={i,j->
i * j
}
//函数类型的lambda表达式的字面值
val squsreFun32: ( Int, Int)->Int={i,j->
i * j
}
val square1Res:Int = square1(1, 2)
Log.e( TAG,"输出square1Res=$square1Res") // 输出square1Res=2
val square2Res:Int = square2(2, 3)
Log.e( TAG,"输出square2Res=$square2Res") //输出square2Res=6
val nimin1Res:Int = nimin1.invoke(3, 4)
Log.e( TAG,"输出nimin1Res=$nimin1Res") //输出nimin1Res=12
val nimin2Res:Int = nimin2.invoke(4, 5)
Log.e( TAG,"输出nimin2Res=$nimin2Res") // 输出nimin2Res=20
val nimin3Res:Int = nimin3.invoke(5,6)
Log.e( TAG,"输出nimin3Res=$nimin3Res") // 输出nimin3Res=30
val squsreFun31Res:Int = squsreFun31.invoke(6, 7)
Log.e( TAG,"输出squsreFun31Res=$squsreFun31Res") // 输出squsreFun31Res=42
val squsreFun32Res:Int = squsreFun32.invoke(7, 8)
Log.e( TAG,"输出squsreFun32Res=$squsreFun32Res") // 输出squsreFun32Res=56
//---------------------------------------------------------------
fun anonymousWrapper(labmda: (Int) -> Int):Unit {
val result:Int = labmda.invoke(1)
Log.e( TAG,"anonymousWrapper:输出result=$result")
}
/** anonymousWrapper:输出result=101
* anonymousWrapper:输出result=102
* anonymousWrapper:输出result=103
* */
//简写的匿名函数 实现anonymousWrapper函数的参数labmda
anonymousWrapper(labmda=fun(i:Int):Int=i+100) // anonymousWrapper:输出result=101
//完整语法形态匿名函数 实现anonymousWrapper函数的参数labmda
anonymousWrapper(labmda=fun(i:Int):Int{
return i+101
}) // anonymousWrapper:输出result=102
// labmda表达式实现anonymousWrapper函数的参数 labmda
anonymousWrapper(labmda={
Log.e( TAG, "在 Lambda 表达式内输出传递过来的参数it=$it") //在 Lambda 表达式内输出传递过来的参数it=1
it+102 //lambda 最后一行的数值 作为返回数据 进行传递
}) // anonymousWrapper:输出result=103
}
}
⒉ 使用一个已存在声明的可调用引用
❶顶级,本地,成员或者扩展函数。例如:String::toInt
❷顶级,成员,或者扩展属性:List::size
❸构造函数:::Regex
class HigherOrderFunctionSuspendActivity3 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
// 函数类型的值可以通过invoke操作符调用
val stringPlus: (String, String) -> String = String::plus
val result01:String = stringPlus.invoke("hello", "world")
Log.e( TAG,"输出result01=$result01") //输出result01=helloworld
}
}
3.使用实现了函数类型接口的自定义类的实例
class HigherOrderFunctionSuspendActivity3 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
// 实现了函数类型接口的自定义类的实例
val intFunction: (Int,Int) -> Int = IntTransformer()
var result0:Int = intFunction.invoke(1, 2)
Log.e( TAG,"输出result0=$result0") //输出result0=3
}
}
class IntTransformer:(Int,Int)->Int {
override fun invoke(p1: Int,p2:Int): Int {
return p1.plus(p2)
}
}
对比:带接收者的函数类型实例化
同函数类型类似,带接收者的函数类型实例化有如下几种方式:
1.使用带接收者函数类型的lambda表达式的字面值 和 带接收者函数类型的匿名函数的字面值 进行赋值
class HigherOrderFunctionSuspendActivity3 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
/**
* Int.()->Int就是带接收者类型的函数类型,和函数类型一样,它表示的是一种类型。其语法有以下特点。
* 开头是接收者的类型Int, 其后是一个点,点后是小括号。
* 小括号内是参数类型和参数名,其中参数名可以省略。
* 箭头后是返回值,如果返回值为Unit,不能省略。
* */
//带接收者函数类型的 单表达式函数
fun Int.square1():Int=this * this
// 带接收者函数类型的 具名函数
fun Int.squareFun():Int{ return this * this }
// 带接收者函数类型的匿名函数的字面值
val squareFun2: Int.()->Int = fun Int.():Int{ return this * this }
//带接收者函数类型的lambda表达式的字面值
val squsreFun3: Int.()->Int ={
Log.e( TAG, "在 Lambda 表达式内输出接收者类型对象this=$this") //在 Lambda 表达式内输出接收者类型对象this=4
this * this
}
val square1Var: Int.()->Int = Int::square1
val square1VarResult:Int = square1Var.invoke(1)
Log.e( TAG,"输出square1VarResult=$square1VarResult") //输出square1VarResult=1
val square2Var: Int.()->Int = Int::squareFun
val square2VarResult:Int = square2Var.invoke(2)
Log.e( TAG,"输出square2VarResult=$square2VarResult") // 输出square2VarResult=4
val square3VarResult:Int = squareFun2.invoke(3)
Log.e( TAG,"输出square3VarResult=$square3VarResult") // 输出square3VarResult=9
var result3:Int = squsreFun3.invoke(4)
Log.e( TAG,"输出result3=$result3") //输出result3=16
}
}
2.使用一个已存在声明成员函数或者扩展函数的可调用引用:
class HigherOrderFunctionSuspendActivity3 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val receiver = FunWithReceiver()
//带接收者的函数类型的函数的声明的变量local1
val firstParam1:FunWithReceiver.(Int) -> Int ={
Log.e( TAG, "在带接收者的函数类型的 Lambda表达式内输出接收者类型对象this=$this 和接收传递过来的参数it=$it")
100//lambda最后一行的结果值 作为返回值
}
//普通函数 声明的变量local11
fun firstParam11(receiver: FunWithReceiver, x: Int):Int{
Log.e( TAG, "在普通函数firstParam11 内输出接收者类型对象receiver=$receiver 和接收传递过来的参数x=$x")
return 101 //返回值进行回调
}
// 如果变量local1 local11 local111 声明为带接收者的函数类型FunWithReceiver.(Int) -> Int,
// 可以使用带接收者的函数类型方式 调用该变量local1 local11 local111
// 可以普通函数类型方式 调用该变量local1 local11 local111
val local1:FunWithReceiver.(Int) -> Int =firstParam1
val local11:FunWithReceiver.(Int) -> Int =::firstParam11
val local111:FunWithReceiver.(Int)->Int=FunWithReceiver::innerFun
// local1(receiver, 1) //正确 可以普通函数类型方式调用该变量local1
val local1Result:Int = local1.invoke(receiver, 1) //正确, 可以使用带接收者的函数类型方式 调用该变量local1
Log.e( TAG,"输出local1Result=$local1Result")
/**输出日志:
* 在带接收者的函数类型的 Lambda表达式内输出接收者类型对象this=FunWithReceiver@f53523c 和接收传递过来的参数it=1
* 输出local1Result=100
* */
// local11(receiver,22) //正确 可以普通函数类型方式调用该变量local11
val local11Result:Int = local11.invoke(receiver,11) //正确, 可以使用带接收者的函数类型方式 调用该变量local11
Log.e( TAG,"输出local11Result=$local11Result")
/** 输出日志:
* 在普通函数firstParam11 内输出接收者类型对象receiver= FunWithReceiver@f53523c 和接收传递过来的参数x=11
* 输出local11Result=101
* */
// local111(receiver,111) //正确 可以普通函数类型方式调用该变量local111
val local111Result:Int = local111.invoke(receiver,111)
Log.e( TAG,"输出local111Result=$local111Result")
/** 输出日志:
* 在FunWithReceiver类里普通函数innerFun 内输出接收者类型对象this= FunWithReceiver@f53523c 和接收传递过来的参数param=111
* 输出local111Result=102
* */
//---------------------------------------------------------------------------
// 如果变量local2 local22 local222 local2222 声明为普通函数类型,
// 则只能用普通函数方式调用该变量local2 local22 local222 local2222,即使其被赋予带接收者函数类型的值
val local2:(FunWithReceiver,Int) -> Int=FunWithReceiver::innerFun
//默认情况下,变量local22会被推导出普通函数类型 。
val local22 = FunWithReceiver::innerFun
val local222: (FunWithReceiver, Int)->Int = firstParam1
val local2222: (FunWithReceiver, Int)->Int = ::firstParam11
// receiver.local2(2) //错误,非带接收者的函数类型(普通函数)local2,不能以接收者加点的方式打开
val local2Result:Int = local2.invoke(receiver, 2) //正确,只能用普通函数方式调用该变量local2
Log.e( TAG,"输出local2Result=$local2Result")
/** 输出日志:
* 在FunWithReceiver类里普通函数innerFun 内输出接收者类型对象this= FunWithReceiver@f53523c 和接收传递过来的参数param=2
* 输出local2Result=102
* */
// receiver.local22(22) //错误,非带接收者的函数类型(普通函数)local22,不能以接收者加点的方式打开
val local22Result:Int = local22.invoke(receiver, 22) //正确,只能用普通函数方式调用该变量local22
Log.e( TAG,"输出local22Result=$local22Result")
/**输出日志:
* 在FunWithReceiver类里普通函数innerFun 内输出接收者类型对象this= FunWithReceiver@f53523c 和接收传递过来的参数param=22
* 输出local22Result=102
* */
//receiver.local222(222) //错误,非带接收者的函数类型(普通函数)local222,不能以接收者加点的方式打开
val local222Result:Int = local222.invoke(receiver, 222) //正确,只能用普通函数方式调用该变量local222
Log.e( TAG,"输出local222Result=$local222Result")
/**
* 在带接收者的函数类型的 Lambda表达式内输出接收者类型对象this= FunWithReceiver@f53523c 和接收传递过来的参数it=222
输出local222Result=100
*/
// receiver.local2222(2222) //错误,非带接收者的函数类型(普通函数)local2222,不能以接收者加点的方式打开
val local2222Result:Int = local2222.invoke(receiver, 2222) //正确,只能用普通函数方式调用该变量local2222
Log.e( TAG,"输出local2222Result=$local2222Result")
/**在普通函数firstParam11 内输出接收者类型对象receiver= FunWithReceiver@f53523c 和接收传递过来的参数x=2222
*输出local2222Result=101
* */
}
}
class FunWithReceiver {
val TAG="HigherOrderFunctionSuspendActivity3"
fun innerFun(param: Int): Int {
Log.e( TAG, "在FunWithReceiver类里普通函数innerFun 内输出接收者类型对象this=$this 和接收传递过来的参数param=$param")
return 102 //返回值进行回调
}
}
函数字面值:
一段代码,本身没有名字,不声明,我们可以把它绑定到一个变量上,通过这个变量操作它,
或者直接作为表达式传递的函数。
函数字面值分2种:函数类型的lambda表达式的字面值 和 函数类型的匿名函数的字面值
注意:与下面的lambda分类对应 匿名函数分类对应
对比:带接收者的函数类型字面值
带接收者的函数类型字面值也分两种:带接收者函数类型的lambda表达式的字面值 和 带接收者函数类型的匿名函数的字面值
注意:与下面的lambda分类对应 匿名函数分类对应
Lambda表达式
只是对匿名函数进一步简写的语法糖,本质是一个东西。Lambda 表达式始终被大括号包围,-> 右箭头用作分割,
Lambda 表达式本质其实是匿名函数,因为底层的实现还是匿名函数来实现的,但是我们在使用的时候其实不太需要关心底层是怎么实现的,Lambda 的出现确实减少了代码量的编写,变得更加简洁。
Lambda表达式语法:
1.lambda 表达式的标准形式基本声明满足三个条件:
❶含实际参数;
❷含函数体(如果函数体为空也要声明出来);
❸以上内容必须被包含在大括号内。lambda表达式总是被大括号包括着。
以上是 lambda 表达式最标准的形式,可能这种标准形式以后在日常开发中很少看见,更多是更加简化的形式。(下面会介绍 lambda 表达式简化的规则)
2.lambda表达式完整句语法形式
显式定义sum的类型为(Int, Int) -> Int的函数类型 的完整lambda表达式
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
lambda 表达式总是在大括号 {} 包裹着,完整语法形式的参数声明在大括号内,函数体 在 -> 符号之后。如果 lambda 的推断返回类型不是 Unit,则 lambda 主体内的最后一个(或者可能是单个)表达式将被默认视为返回值(lambda表达式不能显示的指定返回值的类型)。
也可以给sum直接赋值一个函数类型实例,等于后面的lambda表达式
我们去掉所有可选的参数类型: (Int, Int) -> Int ,编译器能自动推断,简化后就是下面的样式:
val sum = { x: Int, y: Int -> x + y }
3.lambda表达式类型
❶() -> Unit//表示无参数无返回值的Lambda表达式类型
❷(T) -> Unit//表示接收一个T类型参数,无返回值的Lambda表达式类型
❸(T) -> R //表示接收一个T类型参数,返回一个R类型值的Lambda表达式类型
❹(T, P) -> R //表示接收一个T类型和P类型的参数,返回一个R类型值的Lambda表达式类型
❺(T, (P, Q) -> S) -> R //表示接收一个T类型参数和一个接收P、Q类型两个参数并返回一个S类型的值的Lambda表达式类型参数,返回一个R类型值的Lambda表达式类型
(1)无参数语法lambda表达式
语法格式:
val/var <变量名> = { …… }
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//调用 无参数lambda表达式 : noParameter
noParameter() //无参数lambda表达式
}
//无参数lambda表达式 : noParameter
private val noParameter = { Log.e(TAG,"无参数lambda表达式") }
/**
实际源码
private fun noParameter():Unit{
Log.e(TAG,"无参数lambda表达式")
}
*/
}
(2)有参数语法lambda表达式
完整标准lambda表达式语法格式:
val/var <变量名> : (<参数类型>,<参数类型>,...) -> 返回值类型 = { 参数,参数,... -> 操作参数的代码 }
简化后lambda表达式语法格式:
val/var <变量名> = { 参数:<参数类型>,参数:<参数类型>,... -> 操作参数的代码 }
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val hasParameter : Int= hasParameter(4, 2)
Log.e(TAG,"完整标准lambda表达式输出结果hasParameter=$hasParameter") //完整标准lambda表达式输出结果hasParameter=6
val hasParameter2: Int = hasParameter2(4, 2)
Log.e(TAG,"简化后lambda表达式输出结果hasParameter2=$hasParameter2")//简化后lambda表达式输出结果hasParameter2=6
}
//完整标准lambda表达式
val hasParameter: (Int, Int) -> Int = { a, b -> a + b }
//简化后lambda表达式
val hasParameter2 = { a: Int, b: Int -> a + b }
/**实际源码
fun hasParameter(a: Int, b: Int): Int{
return a + b
}
*/
}
(3)把lambda表达式作为一个(高阶)函数中的参数
当 lambda 表达式作为一个函数的参数时,只为其表达式提供了参数类型和返回类型,所以在调用高阶函数的时候要写出该表达式的具体实现。
把lambda表达式作为参数的高阶函数语法格式如下:
fun test( <参数名> : (参数 : 类型, 参数 : 类型, ... ) -> 表达式返回类型) {...}
fun lambdaWrapper(labmda: (Int) -> Int) { labmda.invoke(1)}
lambdaWrapper(labmda={
it +1 //lambda最后一行最为返回值
})
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
//这里我们单独调用 完整标准lambda表达式expression 测试看看
val expression1 = expression.invoke(3,4)
Log.e(TAG,"完整标准lambda表达式expression,输出结果expression1=$expression1") //完整标准lambda表达式expression,输出结果expression1=7
//当 lambda 表达式作为一个函数的参数时,只为其表达式提供了参数类型和返回类型,所以在调用高阶函数的时候要写出该表达式的具体实现。
//把完整标准lambda表达式expression,这里作为函数expressionParameter()的参数numB: (a: Int, b: Int) -> Int 使用的具体实现 { a, b -> a + b }。
val expressionParameter1 = expressionParameter(numA = 2, param1 = 3, param2 = 4, numB = expression)
Log.e(TAG," 输出expressionParameter1=$expressionParameter1") //输出expressionParameter1=14
//直接使用匿名准lambda表达式 作为函数expressionParameter()的参数numB: (a: Int, b: Int) -> Int 使用的具体实现 { a, b -> a + b }。
val expressionParameter2 = expressionParameter(numA = 2, param1 = 3, param2 = 4, numB = { a, b -> a + b })
Log.e(TAG," 输出expressionParameter2=$expressionParameter2") //输出expressionParameter2=14
}
//expressionParameter(numA: Int, numB: (a: Int, b: Int) -> Int) 中参数 numB 只提供了参数类型和返回类型,调用时需要写出参数 numB 的具体实现 { a, b -> a + b }。
private fun expressionParameter(numA: Int, param1:Int, param2:Int, numB: (a: Int, b: Int) -> Int): Int {
return numA * numB.invoke(param1, param2) //a * ( 3 + 4 ),`invoke()` 表示通过 `函数变量` 调用自身。
}
//完整标准lambda表达式expression,这里作为上面函数expressionParameter()的参数numB: (a: Int, b: Int) -> Int 使用的具体实现 { a, b -> a + b }。
val expression: (Int, Int) -> Int = { a, b -> a + b }
}
4.lambdas 语法简化转换
lambda表达式可以大大简化代码写法,也能减少不必要的方法定义,但是带来的副作用是代码的可读性大大降低。下面来介绍一下lambda表达式简化的几种方法,
(1)如果一个函数的最后一个参数是一个函数类型,那么作为对应参数传递的 lambda 表达式可以放在圆括号 () 外:
这种语法也称为尾随 lambda
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
//匿名lambdas 表达式{num: Int -> num > 0 } 是 函数getResult()的参数check:(num:Int)->Boolean具体实现
// 即 check:(num:Int)->Boolean ={num: Int -> num > 0 }
val result :String = getResult(a = 10, check = {num: Int -> num > 0 })
Log.e(TAG,"result=$result") //result=Android
//如果一个函数的最后一个参数是一个函数类型,那么作为对应参数传递的 lambda 表达式可以放在圆括号 () 外
val result2 :String =getResult(a = 10){
num: Int -> num > 0
}
Log.e(TAG,"result2=$result2") //result=Android
}
//check表示传入一个Int类型参数,返回一个Boolean类型返回值
private fun getResult(a:Int,check:(num:Int)->Boolean):String{
val result = if (check.invoke(a)) "Android" else "Java"
return result
}
}
(2)如果 lambda 是被调用的函数的唯一一个参数,函数的圆括号 () 可以被完全省略:
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
//简化前 调用高阶函数 getResult2
val result3:Int = getResult2(check = { num: Int ->
Log.e(TAG,"输出num=$num") //输出num=100
num
})
Log.e(TAG,"result3=$result3") //result3=100
//简化前 调用高阶函数 getResult2
val result4:Int =getResult2() { num: Int ->
Log.e(TAG,"输出num=$num") //输出num=100
num
}
Log.e(TAG,"result4=$result4") //result4=100
//简化后,{ num: Int -> num }是getResult2函数的唯一一个参数,被提到圆括号外面并且圆括号可以被省
val result5:Int =getResult2 { num: Int ->
Log.e(TAG,"输出num=$num") //输出num=100
num
}
Log.e(TAG,"result5=$result5") //result4=100
}
fun getResult2(check:(num:Int)->Int):Int{
//这里还有个大作用 类似回调函数 我在这里输入任何数值 都可以回调给调用getResult2处
val result :Int = check.invoke(100)
return result
}
}
(3) it 单个参数的隐式名称
在高阶函数中,如果 lambda 表达式的参数只有一个,那么不允许声明唯一的参数并且省略了 -> 符号,用 it 表示这个参数的名称。
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
//简化前,num表示参数名称
val result6:String = getResult3() { num: Int ->
//todo 在这里我们获取回调数据 num 数值
Log.e(TAG,"输出回调数值num=$num") // 输出回调数值num=77
//todo 这里我们 用户在调用getResult3()函数对参数(check:(num:Int)->Boolean),使用匿名lambda表达式 { num: Int -> true/false} 具体实现时返回类型=true 对 getResult3()函数里的业务逻辑进行处理
true //lambda 表达式默认最后一行值=true作为返回值 . 传递true 返回类型true
}
Log.e(TAG,"回调结果result6=$result6") //回调结果result6=Android
//简化后,it表示参数名称
val result7:String = getResult3 {
//todo 在这里我们获取回调数据 it 数值
Log.e(TAG,"输出回调数值it=$it") //输出回调数值it=77
//todo 这里我们 用户在调用get Result3()函数对参数(check:(num:Int)->Boolean),使用匿名lambda表达式 { num: Int -> true/false} 具体实现时返回类型= false 对 getResult3()函数里的业务逻辑进行处理
return@getResult3 false //限定的返回语法指定返回值,这里直接返回了 false,使返回类型 false.用了限定的返回语法指定返回值,不再以默认最后一行为返回值
}
Log.e(TAG,"回调结果result7=$result7") //回调结果result7=Java
}
//check表示传入一个Int类型参数,返回一个Boolean类型返回值
private fun getResult3(check:(num:Int)->Boolean):String{
//todo 这就是我们常说的 双向回调数据 双向处理业务逻辑
//todo 这里有个大作用就是:数据回调 通过check.invoke(77)输入77进行数据回调 当用户调用getResult3函数实现时使用 匿名lambda { num: Int -> num} 就可以获取num=77回调的数值
//todo 这里有个大作用就是: 用户在调用getResult3()函数对参数(check:(num:Int)->Boolean),使用匿名lambda表达式 { num: Int -> true/false} 具体实现时返回类型=true/false 对 getResult3()函数里的业务逻辑进行处理
val bool:Boolean = check.invoke(77)
val result :String = if (bool) "Android" else "Java"
Log.e(TAG," 参数check返回结果check =$bool") //参数check返回结果check =true 参数check返回结果check =false
return result
}
}
it 不是关键字,表示单个参数的隐式名称。
以后开发中我们更多的是使用简化版的 lambda 表达式,因为标准版的 lambda 表达式还是有点啰嗦,比如实参类型就可以省略,Kotlin 支持根据上下文智能推导出类型,所以可以省略,摒弃啰嗦的语法,下面总结了一下:
注意:语法简化是把双刃剑,使用简单方便,但是不能滥用,也需要考虑代码的可读性。下图中的 lambda 化简成最简形式用 it 这种,一般在多个 lambda 嵌套时不建议使用,大大降低代码可读性,最后连开发者都不知道 it 代表什么。
5.从lambda表达式返回值
如果 lambda 表达式有返回值,则 lambda 主体内的最后一行表达式将被视为返回值返回,即lambda会将最后一条语句作为其返回值。如果使用限定的返回语法显式地从 lambda 返回一个值,则不会再以默认的最后一行返回值。
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
//调用
getResult4(a=11){ //it 表示参数名称
Log.e(TAG,"输出回调数值it=$it") //输出回调数值it=11
// val result = it > 0
val result = true
result//lambda 表达式默认最后一行值=true作为返回值
}
val result8:String = getResult4(a=22) {
Log.e(TAG,"输出回调数值it=$it") //输出回调数值it=22
// val result = it > 0
val result = false
return@getResult4 result //限定的返回语法指定返回值,这里直接返回了true/false,使用了限定的返回语法指定返回值,不再以默认最后一行为返回值
}
Log.e(TAG,"回调结果result8=$result8") //回调结果result8=Java
}
//check表示传入一个Int类型参数,返回一个Boolean类型返回值
fun getResult4(a: Int, check: (num:Int) -> Boolean): String {
val result = if (check.invoke(a)) "Android" else "Java"
return result
}
}
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val arrayStr = arrayOf("sum", "java", "android", "kotlin")
val result9 = arrayStr.filter(predicate = {
Log.e(TAG, " it1=$it") //it1=sum it1=java it1=android it1=kotlin
return@filter it.length == 6 //使用了限定的返回语法指定返回值 it.length==5 =true
}) //点的左边还是 arrayStr 集合
.sortedBy(selector = {
Log.e(TAG, " it2=$it")
it //lambda 表达式默认最后一行值it 作为返回值
})
//点的左边还是 arrayStr 集合
.map(transform = {
Log.e(TAG, " it3=$it") //it3=kotlin
val toUpperCase: String = it.toUpperCase()
toUpperCase //lambda 表达式默认最后一行值toUpperCase 作为返回值
})
Log.e(TAG,"result9=$result9") // result9=[KOTLIN]
}
}
6.下划线_代替未使用的变量参数(自1.1以来)
如果 lambda 参数未使用,你可以用下划线_ 来代替参数的名称:
下划线 _ 表示未使用的参数,不处理这个参数。
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
//如果参数未使用,用下划线 `_` 来代替它的名称
val radioGroup = RadioGroup(this)
//原型
radioGroup.setOnCheckedChangeListener { group, checkedId -> }
//简化后,参数group未被使用,用下划线 `_` 来代替
radioGroup.setOnCheckedChangeListener { _, checkedId ->
if (checkedId == 0) {
Log.e(TAG,"被选中")
}
}
}
}
7.使用typealias关键字给Lambda类型命名
我们试想一个场景就是可能会用到多个 Lambda表达式,但是这些 Lambda表达式的类型很多相同,我们就很容易把一大堆的Lambda类型重复声明或者Lambda类型声明太长不利于阅读。实际上不需要,Kotlin 反对一切啰嗦语法,它都会给你提供一系列的解决办法,让你简化代码的同时又不降低代码的可读性。
不使用使用typealias关键字
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val result10 = strEmpty.invoke("fang")
Log.e(TAG,"输出result10=$result10") //输出result10=fang
strTrue("ming")
}
private val strEmpty:(String)->String={
if(it.isNotEmpty()){
Log.e(TAG,"it = $it") //it = fang
}
it//lambda表达式默认最后一行的数值 作为返回值
}
val strTrue:(String)->Unit={
if (it == "ming") {
Log.e(TAG,"it = $it") //it = ming
}
//这里 lambda表达式 没有返回值
}
}
使用使用typealias关键字声明 (String) -> Unit 类型
// 在最顶层 使用 typealias 关键字声明 (String) -> Unit 类型:
typealias StringEmpty = (String) -> Unit
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
strEmpty2.invoke("fang")
strTrue2.invoke("ming")
}
private val strEmpty2: StringEmpty = {
if (it.isNotEmpty()) {
Log.e(TAG,"it = $it") //it = fang
}
//这里 lambda表达式 没有返回值
}
private val strTrue2:StringEmpty={
if (it == "ming") {
Log.e(TAG,"it = $it") //it = ming
}
//这里 lambda表达式 没有返回值
}
}
Lambda表达式分类:
在 Kotlin 中实际可以将 lambda 表达式分为两大类:一个是普通函数类型的lambda表达式的字面值,另一个是带接收者函数类型的lambda表达式的字面值
1.普通函数类型的lambda表达式的字面值
普通的 lambda 表达式类似对应普通的函数声明,都有一个圆括号括起来的参数类型列表以及一个返回类型
函数类型 | 参数类 型列表 | 返回值 类型 | 备注 |
(A, B) -> C | A,B | C | 表示接受类型分别为 A 与 B 两个参数并返回一个 C 类型值的函数类型 |
(A) -> C | A | C | |
() -> C | 空 | C | |
(A, B) -> Unit | A,B | Unit | 表示接受类型分别为 A 与 B 两个参数,返回类型Unit |
(T) -> Unit | T | 空 | (T) -> Unit通过形参T可将对象作为实参传给函数,所以函数体里能通过it或者指定名称的方式来访问该对象 |
() -> Unit | 空 | 空 | 参数类型列表可以为空,返回类型Unit(Unit 返回类型不可省略) |
2. 带接收者函数类型的lambda表达式的字面值
在 Kotlin 中,带有接收者的函数类型是一种特殊的函数类型,它允许在lambda表达式内访问接收者对象的成员。这种类型的主要用途之一是支持 DSL(领域特定语言)的构建,以提供更具表达性的 API。
函数类型有一个额外的接收者类型T,该接收者类型T在表示法中的点之前指定,
带有接收者的函数类型,例如 A.(B) -> C
,可以用特殊形式的函数字面值实例化—— 带有接收者的函数字面值。
Kotlin 提供了调用带有接收者(提供接收者对象)的函数类型实例的能力。
在这样的函数字面值内部,传给调用的接收者对象成为隐式的 this
,以便访问接收者对象的成员而无需任何额外的限定符,亦可使用 this
表达式 访问接收者对象。
这种行为与扩展函数类似,扩展函数也允许在函数体内部访问接收者对象的成员。
带接收者函数的 lambda 表达式类似对应扩展函数。扩展函数就是这种声明接收者类型,然后使用接收者对象调用成员函数,实际内部就是通过这个接收者对象实例直接访问它的属性和方法。
为了表示当前的 接收者对象 我们使用 this 表达式:
- 在类的成员中,this 指的是该类的当前对象。
- 在扩展函数或者带有接收者的函数字面值中,
this
表示在点左侧传递的 接收者 对象A。
函数类型 | 额外的接收者的类型 | 参数类型列表 | 返回值类型 | 备注 |
A.(B) -> C | A | B | C | 表示可以在 A 的接收者对象上以一个 B 类型参数来调用并返回一个 C 类型值的函数。 类型A的接受者实例(一个自定义对象/this)调用该函数传递参数B 返回一个C类型的值 |
A.() -> C | A | 无 | C | |
T.() -> Unit | T | 无 | 空 | T.()->Unit 等同于为T定义了一个无参数的扩展函数,所以在函数体内可以直接通过this或省略来访问T代表的对象 |
block: T.(T) -> Unit | 函数体中可以直接使用T代表的对象,即用this代表对象 | |||
block: T.() -> Unit | fun T.apply(block: T.() -> Unit): T { block(); return this } T.()->Unit 中用this代表对象,而this的使用一般是一个类的成员函数中用来表示该类的实例对象本身。 T.()->Unit相当于是给类T定义了一个扩展函数,该函数没有形参,没有返回值,所以在函数体内可以直接通过this或省略来访问T代表的对象.当然我们也可以增加参数与返回值,道理是一样的。 正是因为T.()为T的扩展函数,所以可以在函数体里直接访问T对象的属性或者成员函数。 | |||
block: (T) -> Unit | fun T.also(block: (T) -> Unit): T { block(this); return this } 将T表示的对象作为实参通过函数参数传递进来,供函数体使用 | |||
block: () -> Unit | fun <T> T.doWithTry(block:()->Unit){ block.invoke()} ()->Unit与T表示的对象没有直接联系,只能通过外部T实例的变量来访问对象 | |||
针对带接收者的 lambda 表达式在 Kotlin 标准库中也是非常常见的,比如 with,apply 等标准函数的声明:t针对带接收者的 lambda 表达式在 Kotlin 标准库中也是非常常见的,比如 with,apply 等标准函数的声明:
/**
* 官方also
public inline fun <T> T.also(block: (T) -> Unit): T {
block(this)
return this
}
*/
val person= Person("张三")
person.also(block={
//没有指定参数名字,必须用it代指参数 it:Person = person
it.age=20
it.sex=0
Log.e(TAG, "输出结果it=${it.toString()}") //输出结果it=com.my.runalone_coroutinedemo.bean.Person@67f1dc6
})
/**
* 官方apply
public inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
*/
person.apply(block={
//直接访问Person的属性
//this:Person = person
this.age=20 //this可以省略
this.sex=1 //this可以省略
Log.e(TAG, "输出结果this=${this.toString()}") //输出结果this=com.my.runalone_coroutinedemo.bean.Person@67f1dc6
})
案例:
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
//带接收者Int.(Int) -> Int的Lambda表达式案例:
//使用
val ee1:Int = ee(1,2) //像普通函数那样调用
Log.e(TAG,"ee1=$ee1") // ee1=3
val ee2:Int = ee.invoke(2,3) //使用invoke
Log.e(TAG,"ee2=$ee2") // ee1=5
val ee3:Int = 3.ee(4) //像扩展函数那样调用
Log.e(TAG,"ee3=$ee3") // ee3=7
val result25:Int = method2(bb={
Log.e(TAG,"输出接收者例this=$this 输出其他参数it=$it") //输出接收者例this=2 输出其他参数it=3
// 获取调用者this的成员函数/属性
val result:Int = this.plus(it)
result //lambda最后一行作为返回数据 result
})
Log.e(TAG,"result25=$result25") //result25=1000
}
//匿名函数
//写法一:指定参数类型 other: Int
val cc = fun Int.(other: Int) :Int = this.plus(other)
val cc2 = fun Int.(other: Int) :Int {
return this.plus(other)
}
//写法二:指定实例类型
val dd: Int.(other:Int) -> Int = fun Int.(other: Int) :Int = this.plus(other)
val dd2: Int.(other:Int) -> Int = fun Int.(other: Int) :Int{
return this.plus(other)
}
val dd3: Int.(Int) -> Int = fun Int.(other:Int) :Int{
return this.plus(other)
}
// 定义一个函数类型的变量ee 只有变量ee才具有 传参/赋值 的功能。
//带接收者Int.(Int) -> Int函数类型 赋值一个 Lambda表达式{}
val ee: Int.(Int) -> Int = {
//在 Lambda 函数体中(即代码块中)可以直接调用接收者this(隐士)的成员属性/函数
Log.e(TAG,"输出接收者例this=$this") // 输出接收者实例this=1 输出接收者实例this=2 输出接收者实例this=3
var result:Int = this.plus(it) // 获取调用者this的成员函数/属性
result //lambda最后一行作为返回数据
}
// 带接收者Int.(Int) -> Int函数类型的变量bb 具有传参的功能
fun method2(bb:Int.(Int)->Int):Int{
// val result:Int = 1.bb(2)
// return result
//或者这样写
val callBackResult:Int = bb.invoke(2,3)
Log.e(TAG,"callBackResult=$callBackResult") //callBackResult=5
return 1000 //随便自定义一个数值作为返回值
}
}
Lambda表达式常用场景:
1.场景一:lambda 表达式与集合一起使用是最常见的场景
lambda 表达式与集合一起使用是最常见的场景,可以各种帅选、映射、变换操作符和对集合数据进行各种操作,非常灵活,类似使用 RxJava 函数式编程,Kotlin 在语言层面无需增加额外库,就给你提供函数式编程API。
val lists = arrayListOf("Koin", "Android", "Kotlin")
//filter(predicate: (T) -> Boolean): List<T>
lists.filter(predicate={
//给函数类型predicate: (T) -> Boolean 返回 Boolean
val startsWith :Boolean = it.startsWith("K")
startsWith//lambda最后一行作为返回值startsWith 作为函数类型predicate的返回值类型Boolean结果
})
//点左边还是lists集合对象 // map(transform: (T) -> R): List<R>
.map(transform={
Log.e(TAG,"输出it=$it") //输出it=Koin 输出it=Kotlin
"$it v1.9.0"
})
//点左边还是lists集合对象 //forEach(action: (T) -> Unit)
.forEach (action={
Log.e(TAG,"遍历输出集合每个元素 it=$it") // 遍历输出集合每个元素it=Koin v1.9.0 遍历输出集合每个元素 it=Kotlin v1.9.0
//lambda最后一行不需要返回值
})
/**
* 遍历输出集合每个元素it=Koin v1.9.0
* 遍历输出集合每个元素 it=Kotlin v1.9.0
* */
2 .场景二:lambda表达式替代原有匿名内部类,但是注意只能替代单抽象方法的类。
textView.setOnClickListener(new View.OnClickListener() {
@Overridepublic void onClick(View v) {
//TODO
}
});
用 Kotlin lambda 实现,等价于:
textView.setOnClickListener {
//TODO
}
3 场景三:定义 Kotlin 扩展函数或者说需要把某个操作或者函数当作参数值传入某个函数的时候。
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
showDialog(content="关机警告弹框",negativeText="取消",positiveText="关机",
negativeAction={
//取消操作 diss()
},
positiveAction={
// 关机操作 sendCommand()
}
)
}
fun showDialog(content: String = "弹框", negativeText: String = "取消", positiveText: String = "确定", negativeAction: (() -> Unit)? = null, positiveAction: (() -> Unit)? = null) {
AlertDialog.Builder(this)
.setMessage(content)
.setNegativeButton(negativeText) { _, _ ->
negativeAction?.invoke()//通过invoke() 进行回调 但是没有数据回调
}
.setPositiveButton(positiveText) { _, _ ->
positiveAction?.invoke() //通过invoke() 进行回调 但是没有数据回调
}
.setCancelable(true)
.create()
.show()
}
}
Lambda表达式的作用域中访问变量
1. Kotlin和Java访问局部变量的区别
(1)在 Java 函数内部定义一个匿名内部类或者 lambda,内部类访问的局部变量必须是 final 修饰的,意味着在内部类内部或者 lambda 表达式的内部无法修改函数局部变量的值。
//Java
public class ExampleActivity extends AppCompatActivity implements View.OnClickListener {
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_example);
final int count=0;//需要使用final修饰
findViewById(R.id.btn_syn).setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
System.out.println(count);//在匿名OnClickListener内部类访问count必须要是final修饰的
}
});
}
(2)Kotlin 中在函数定义的内部类或 在lambda表达式作用域内,既可以访问 final 修饰的变量,也可以访问非 final 修饰的变量,意味着 lambda 内部是可以直接修改函数局部变量的值。
通过以上对比发现,Kotlin 使用 lambda 比 Java 中使用 lambda 更灵活,访问限制更少。Kotlin 中的 lambda 表达式是真正意义上支持闭包,而 Java 中的 lambda 则不是。下面来分析 Kotlin 是怎么做到这一点的?
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val textView:TextView = findViewById<TextView>(R.id.textview)
var count = 0 //声明非final类型 变量count
val countFinal = 0//声明final类型 变量countFinal
textView.setOnClickListener {
//lambda内 访问并修改非final修饰的变量 count
Log.e(TAG,"访问并修改非final修饰的变量count=${count++}")
//lambda内 访问final修饰的变量countFinal,和Java一样
Log.e(TAG,"访问final修饰的变量countFinal=${countFinal}")
}
}
}
/** 访问并修改非final修饰的变量count=0
* 访问final修饰的变量countFinal=0
* 访问并修改非final修饰的变量count=1
* 访问final修饰的变量countFinal=0
* 访问并修改非final修饰的变量count=2
* 访问final修饰的变量countFinal=0
* 访问并修改非final修饰的变量count=3
* 访问final修饰的变量countFinal=0
* 访问并修改非final修饰的变量count=4
* 访问final修饰的变量countFinal=0
* */
2. Kotlin中Lambda表达式的变量捕获(访问调用修改)以及原理
注意:对于在 lambda 表达式内部访问修改局部变量的值,只有当这个 lambda 表达式被执行的时候触发修改局部变量的值。如果 lambda 表达式无法被触发执行,局部变量值就无法被访问修改。
什么是变量捕获?
通过上面的例子,我们知道在 Kotlin 中既能访问 final 修饰的变量也能访问和修改非 final 修饰的变量。这里涉及到一个概念lambda表达式的变量捕获,实际上就是 lambda 表达式在函数体内可以访问和修改外部变量,我们就称为这些变量被 lambda 表达式捕获了。
有了这个概念我们把上面的总结一下:
❶在 Java 中 lambda 表达式只能捕获 final 修饰的变量;
❷在 Kotlin 中 lambda 表达式既能捕获 final 修饰的变量也能访问和修改非 final 修饰的变量。
变量捕获(访问调用修改)的原理
我们知道函数局部生命周期属性这个函数,当函数执行完毕,局部变量也被销毁了,但是如果这局部变量被 lambda 捕获了,那么使用这个局部变量的代码将被存储起来等待稍后再次执行,也就是被捕获的变量可以延迟生命周期的。
(1)lambda 捕获 final 修饰的局部变量原理:
局部变量的值和使用这个变量的表达式被存储起来;
(2)lambda 捕获非 final 修饰的局部变量原理:
实际是 lambda 表达式还是只能捕获 final 修饰的变量,而为什么 Kotlin 能做到修改非 final 变量的值,Kotlin 在语层面做了桥接包装,它把非 final 修饰的变量使用一个 Ref 包装类包装起来,然后外部保留 Ref 包装类的引用是 final 的,然后表达式和这个 final 修饰的 Ref 引用一起存储,随后在 lambda 内部修改变量的值实际上是通过这个 final 的 Ref 包装类引用去修改的。
最后通过查看 Kotlin 修改非 final 局部变量的反编译成的 Java 代码:
kotlin代码
class LambdasActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val textView:TextView = findViewById<TextView>(R.id.textview)
var count = 0//声明非final类型 变量count
textView.setOnClickListener {
//lambda内 访问并修改非final修饰的变量 count
Log.e(TAG,"访问并修改非final修饰的变量count=${count++}")
}
}
}
反编译成java代码后:
操作:Android studio > Tools > Kotlin > Show Kotlin Bytecode > 点击 Decompile > Kotlin类即可反编译成 Java类
public final class LambdasActivity extends AppCompatActivity {
protectedvoidonCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView textView = findViewById<TextView>(R.id.textview)
final IntRef count=new IntRef();//IntRef特殊的包装器类型,final修饰IntRef的引用count
count.element = 0;//包装器内部的非final变量
textView.setOnClickListener((OnClickListener)(new OnClickListener() {
public final void onClick(View it) {
IntRef var10000 = count;
int var2;
var10000.element = (var2 = var10000.element) + 1;
booleanvar3=false;
System.out.println(var2);
}
}));
}
}
Lambda 表达式的成员引用:
1. Lambda 表达式为什么使用成员引用?
Lambda 表达式可以直接把一个代码块作为一个参数传递给一个函数,但是如果要当做参数传递的代码已经被定义成了函数,此时还需要重复写一个代码块传递过去吗?肯定不是,这时候就需要 把函数转换成一个值,这种方式称为 成员引用。
Lambda表达式{ p: Person2-> p.age } 可以用成员引用 Person2::age 替换。 成员引用和 调用函数的lambda表达式 具有一样的类型,所以可以相互转换。
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val persons = arrayListOf(Person2("Java", 20), Person2("Android", 5))
// Lambda表达式{ p: Person2-> p.age } 可以用 成员引用类型 Person2::age 替换。 成员引用类型 和 调用函数的lambda表达式 具有一样的类型,所以可以相互转换。
val result12:Person2 = persons.maxBy(selector = { p: Person2 -> p.age})
Log.e(TAG, "输出最终返回的最大数据值 result12= $result12")
val result13:Person2 = persons.maxBy(selector = Person2::age)
Log.e(TAG, "输出最终返回的最大数据值 result13= $result13")
}
}
/**
*输出最终返回的最大数据值 result12= Java 20
* 输出最终返回的最大数据值 result13= Java 20
* */
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val persons = arrayListOf(Person2("Java", 20), Person2("Android", 5))
// maxBy(selector: (T) -> R): T? 根据传入的条件(最大max age)来遍历集合,从而找到该条件下的最大值
val result11:Person2 = persons.maxBy(selector = { p: Person2 ->
//具体实现的lambda表达式回调参数数据 p:Person2= T = 集合元素
Log.e(TAG, "输出回调参数数据 p = ${p.toString()}")
p.age //lambda表达式 最后一行默认返回的数据 p.age=R
}) //返回 集合元素=T
Log.e(TAG, "输出最终返回的最大数据值 result11= $result11")
}
}
/**
* 输出回调参数数据 p = Java 20
* 输出回调参数数据 p = Android 5
* 输出最终返回的最大数据值 result11= Java 20
*
* */
2 .成员引用的基本语法
上面这种用法称为成员引用,它提供简明语法,来创建一个单个方法或者单个属性的函数值,使用 :: 运算符来转换。成员引用由类名,双冒号::,成员三个元素组成。
成员是函数名表示引用函数,成员是属性表示引用属性。
3 .成员引用的使用场景
(1)最常见的使用方式是类名+双冒号::+成员(属性或函数):
val persons = arrayListOf(Person2("Java", 20), Person2("Android", 5))
val result13:Person2 = persons.maxBy(selector = Person2::age)
Log.e(TAG, "输出最终返回的最大数据值 result13= $result13")
/**
*
* 输出最终返回的最大数据值 result13= Java 20
* */
(2)还可以引用顶层函数,这种情况省略了类名称,直接以 :: 开头。成员引用 ::salute 被当作实参传递给库函数 run(),它会调用相应的函数:
//顶层函数
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
// run(block: T.() -> R): R
//使用函数引用前
this@HigherOrderFunctionSuspendActivity.run(block={
//由此可知 this@HigherOrderFunctionSuspendActivity=HigherOrderFunctionSuspendActivity=T
this@HigherOrderFunctionSuspendActivity.saulte("方")
})
//使用函数引用简化后
this@HigherOrderFunctionSuspendActivity.run(block={
//由此可知 this@HigherOrderFunctionSuspendActivity=HigherOrderFunctionSuspendActivity=T
this@HigherOrderFunctionSuspendActivity::saulte.invoke("明")
})
/**
* saulte=方
* saulte=明
* */
}
fun saulte(str:String) = Log.e(TAG,"saulte=$str")
}
(3)如果 lambda 要委托给一个接收多个参数的函数,提供成员引用代替它将会非常方便:
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
//调用
nextAction(Person2("fmf",33), "明天上班")
/**
* sendEmail=fmf 33,明天上班
* */
}
private fun sendEmail(person2:Person2, message:String)=Log.e(TAG,"sendEmail=${person2.toString()},$message")
//定义一个有多个参数的 lambda表达式的变量action
val action :(p:Person2,m:String)->Unit = {person2:Person2,message:String->
sendEmail(person2, message)
}
//使用成员引用代替
val nextAction=::sendEmail
}
(4)成员引用用于构造方法。可以用构造方法的引用存储或者延期执行创建类实例的动作,构造方法的引用方式是在双冒号后指定类名称: ::类名
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val getPerson2 =::Person2 //创建的实例动作就保存成了值
Log.e(TAG,"构造方法 == " + getPerson2("Kotlin", 3)) // 构造方法 == Kotlin 3
}
}
(5)成员引用还可以使用同样的方式引用扩展函数:
Extend.kt
// 这是Person2的一个扩展函数,判断是否成年
fun Person2.isChild():Boolean{
return this.age>18
}
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val isChild = Person2::isChild
val b = isChild(Person2("Java", 20))
Log.e(TAG, "isChild == $b") //isChild == true
}
}
4. 绑定引用
Kotlin1.1 以上允许你使用成员引用语法 捕捉特定实例对象上的方法引用。
Kotlin1.1之前显式写出 lambda{ person.age } 而不是使用绑定成员引用:person::age。
Kotlin1.1之后可以使用绑定成员引用:person::age。
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val person2 = Person2("Android", 24) //创建实例
Kotlin1.1之前显式写出 lambda{ person.age } 而不是使用绑定成员引用:person::age。
val personAge1:()->Int={
person2.age
}
val result14:Int = personAge1.invoke()
Log.e(TAG, "personAge1 == ${result14}") // personAge1 == 24
//Kotlin1.1之后可以使用绑定成员引用:person::age。
val personAge2:()->Int=person2::age
val result15:Int = personAge2.invoke()
Log.e(TAG, " personAge2 == ${result15}") // personAge2 == 24
}
}
}
5.成员引用
需要传入函数形参时,现有的具名函数刚好满足要求(函数类型以及代码逻辑),通过成员引用将该函数转换成一个值传递它。
成员变量/函数 | 实例名 :: 属性名/函数名 | |
扩展属性/函数 | 类名() :: 属性名/函数名 | |
单例或伴生对象中的变量/函数 | 类名 :: 属性名/函数名 | |
顶层属性/方法 | :: 属性名/函数名 (不属于任何一个类,类型省略) | |
构造函数 | :: 类名 |
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
// 调用函数类型的变量aa,
// 函数类型的变量aa具有传递参数功能 传递参数1,2
val result21:Int = aa.invoke(1, 2)
Log.e(TAG,"result21=$result21") // result21=3
// 调用函数类型的变量aa,
// 函数类型的变量aa具有传递参数功能 传递参数2,3
val result22:Int = aa(2,3) //等效于 aa.invoke(2,3)
Log.e(TAG,"result22=$result22") // result22=5
//调用method函数
// 这里直接传递具体参数lambda表达式,具体实现method函数的函数类型的变量bb参数
val result23:String = method(bb = { x: Int, y: Int ->
Log.e(TAG,"lambda: x=$x, y=$y") // x=2, y=3
x + y //lambda最后一行作为返回值
})
Log.e(TAG,"result23=$result23") //result23=liuliang
//调用method函数
// 这里直接传递具体参数使用::summ,具体实现method函数的函数类型的变量bb参数
val result24:String = method(bb=::summ)
Log.e(TAG,"result24=$result24") //result24=liuliang
//看不懂 以后再说吧
(::summ).invoke(2,3) //调用,等效于 (::sum).invoke(2,3)
}
//这是一个具名函数(普通函数)
//具名函数是没有invoke()功能
private fun summ(x: Int, y: Int): Int {
Log.e(TAG,"summ : x=$x,y=$y") // lambda: x=2, y=3
return x +y
}
//具名函数加上::双冒号就变成一个 返回类型是函数类型的 变量aa, 只有变量aa才具有 传参/赋值 的功能。
//aa的类型 KFunction2<Int, Int, Int>
//函数类型的变量aa具有invoke()功能,具名函数summ是没有的。
//给函数类型的变量aa 具有赋值功能 赋值::summ
val aa :(x: Int, y: Int)->Int = ::summ
// 函数类型的变量bb 具有传参的功能
fun method(bb :(x: Int, y: Int)->Int):String {
val result:Int = bb.invoke(2, 3)
Log.e(TAG,"输出result=$result") // 输出result=5
return "liuliang"
}
}
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
// 调用函数类型的变量nextAction,
// 函数类型的变量nextAction具有传递参数功能,传递参数Person2("fmf",33) "明天上班"
nextAction.invoke(Person2("fmf",33), "明天上班")
/**
* sendEmail=fmf 33,明天上班
* */
// 调用函数类型的变量nextAction2,
// 函数类型的变量nextAction具有传递参数功能,传递参数Person2("fmf",33) "明天上班"
nextAction2.invoke(Person2("fmf2",34),"明天休息")
/**
* nextAction2 lambda =fmf2 34,明天休息
* */
}
//这是一个具名函数(普通函数)
//具名函数是没有invoke()功能
private fun sendEmail(person2:Person2, message:String):Unit{
Log.e(TAG,"sendEmail=${person2.toString()},$message") //sendEmail=fmf 33,明天上班
}
//具名函数加上::双冒号就变成一个 返回类型是函数类型的 变量nextAction, 只有变量nextAction才具有 传参/赋值 的功能。
//nextAction的类型 KFunction2<Person2, String, Unit>
//函数类型的变量nextAction具有invoke()功能,具名函数sendEmail是没有的。
//给函数类型的变量nextAction 具有赋值功能 赋值::sendEmail
private val nextAction:(person2:Person2, message:String)->Unit=::sendEmail
//给函数类型的变量nextAction2 具有赋值功能 赋值 一个有多个参数的 lambda表达式
val nextAction2 :(person2:Person2,message:String)->Unit = {person2:Person2,message:String->
Log.e(TAG,"nextAction2 lambda =${person2.toString()},$message") //nextAction2 lambda =fmf2 34,明天休息
}
}
匿名函数
匿名函数分2种: 函数类型的匿名函数的字面值 和 带接收者函数类型的匿名函数的字面值
1. 函数类型的匿名函数的字面值
lambda表达式不能显示的指定返回值的类型。如果需要显示指定返回值的类型,则需要使用匿名函数。 匿名函数可以明确指定返回类型,匿名函数与常规函数不同的是没有函数名。匿名函数就是无需定义函数名称,其函数体也可以是表达式或代码块.
fun(x: Int, y: Int): Int {
return x + y
}
注意:匿名函数参数总是在括号内传递。允许将函数放在括号外的简写语法仅适用于lambda表达式。
lambda 表达式和匿名函数之间的另一个区别是非本地返回的行为。没有标签的 return 语句总是从使用 fun 关键字声明的函数返回。
这意味着 lambda 表达式中的 return 将从封闭函数返回,而匿名函数内部的 return 将从匿名函数本身返回。
案例:匿名函数的使用 与lambda表达式使用的区别
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
/**
这里使用匿名函数 fun(i:Int):Int{
...
return result
}
实现 函数类型参数 param
*/
val result27:String = anonymousWrapper(param=fun(i:Int):Int{
Log.e(TAG,"输出传递过来的参数i=$i") //输出传递过来的参数i=1
val result:Int=i+100 //接收这个Int 类型的参数i
return result //把 result 作为Int类型 显示结果 返回传递
})
Log.e(TAG,"输出函数anonymousWrapper的返回结果result27=$result27")
/** 输出传递过来的参数i=1
* 输出返回的Int类型数据结果result=101
* 输出函数anonymousWrapper的返回结果result27=fmf
*/
//简写
val result272:String = anonymousWrapper(param=fun(i):Int{
return i+100
})
//简写
val result273:String = anonymousWrapper(param=fun(i)=i+100)
/** 这里使用lambda表达式 {
...
return
}
实现 函数类型参数 param
* */
val result28:String =anonymousWrapper(param = {
Log.e(TAG,"输出传递过来的参数it=$it") // 输出传递过来的参数it=1
val result:Int=it+100 //接收这个Int 类型的参数i
result// lambda 的最后一行 隐士作为结果 进行返回传递
})
Log.e(TAG,"输出函数anonymousWrapper的返回结果result28=$result28")
/**输出传递过来的参数it=1
*输出返回的Int类型数据结果result=101
*输出函数anonymousWrapper的返回结果result28=fmf
* */
}
fun anonymousWrapper(param : (Int) -> Int):String{
val result:Int = param.invoke(1) //传递一个Int类型 参数
Log.e(TAG,"输出返回的Int类型数据结果result=$result") //输出返回的Int类型数据结果result=101
return "fmf" // 作为函数anonymousWrapper返回类型
}
}
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val items = arrayOf(1, 2, 3, 4, 5)
//返回符合条件的元素的列表 filter(predicate: (T) -> Boolean): List<T>
val list1 = items.filter(
//匿名函数写法
predicate=fun(item:Int):Boolean = item > 2 //item > 2 结果就是predicate的返回值
)
Log.e(TAG, "返回符合条件的元素的列表list1 == $list1")//返回符合条件的元素的列表list1 == [3, 4, 5]
val list2 = items.filter(
//匿名函数写法
predicate=fun(item:Int):Boolean{ return item > 2 } //item > 2 结果就是predicate的返回值
)
Log.e(TAG, "返回符合条件的元素的列表list2 == $list2")//返回符合条件的元素的列表list2 == [3, 4, 5]
//filter(predicate: (T) -> Boolean): List<T>
val list3 = items.filter(
//lambda表达式写法
predicate={item:Int->
item > 2 // lambda默认最后一行 作为predicate的返回值
}
)
Log.e(TAG, "返回符合条件的元素的列表list3 == $list3") // 返回符合条件的元素的列表list3 == [3, 4, 5]
}
}
2. 带接收者函数类型的匿名函数的字面值
在 Kotlin 中,提供了 使⽤指定接收者对象 调用 函数显式声明 的功能。
在函数显式声明的函数体中,可以调⽤该接收者对象上的对应成员⽅法/属性,⽽⽆需任何额外的限定符。(这有点类似于扩展函数,它允你在函数体内访问接收者对象的成员。)
//指定接收者对象=this=TextView
// 接收者对象this 调用 Lambda 表达式block={}
fun TextView.isBold()=this.apply(block={
// 调用/访问 该接收者对象this的成员
this.paint.isFakeBoldText=true
})
案例1:
函数显式声明的类型: 是⼀个带有接收者的函数类型 Int.(other:Int) -> Int
var sum21: Int.(other:Int) -> Int = {
Log.e(TAG, "sum21 输出 接受者对象this=$this,其他参数it=$it") // sum21 输出 接受者对象this=1,其他参数it=2
val result:Int = this.plus(it) //这里直接调用了接受者对象this的成员方法
result//lambda最后一行最为返回值
}
函数显式声明:可以像接收者的一个方法一样被调用
//函数显式声明 可以像接收者的一个方法一样被调用
// 传入接受者对象1 调用接受者对象1的smu21方法
var num7:Int = 1
val sum21Result:Int = num7.sum21(2)
Log.e(TAG, "sum21Result=$sum21Result") //sum21Result=3
案例2:
如果是匿名函数,我们直接 指定 函数显式声明的接收者类型
若你需要使⽤带接收者的函数类型Int.(other:Int) -> Int声明⼀个变量sum22,并在之后使⽤它,这将⾮常有⽤
var sum22 : Int.(other:Int) -> Int = fun Int.(other: Int): Int{
Log.e(TAG, "sum22 输出 接受者对象this=$this,其他参数it=$other") // sum22 输出 接受者对象this=1,其他参数it=3
var result:Int = this.plus(other)
return result
}
函数显式声明:可以像接收者的一个方法一样被调用
//函数显式声明:可以像接收者的一个方法一样被调用
// 调用接受者对象1 的sum22方法
val sum22Result:Int = num7.sum22(3)
Log.e(TAG, "sum22Result=$sum22Result") //sum22Result=4
案例3:
inline fun <T, R> T.myWith32( block: T.(Any) -> R): R {
var result:R = block.invoke(this,4)
return result
}
val myWith32_Result: Int = num7.myWith32(block = {
Log.e(TAG, "myWith32 输出当前对象this=${this}, it=$it") // myWith32 输出当前对象this=1, it=4
var result:Int = this.plus(it as Int)
result//把lambda最后一行作为返回值
})
Log.e(TAG, "myWith32_Result=$myWith32_Result") //myWith32_Result=5
案例4:
private fun stringMapper( mapper:(String)->Int):Int{
val result:Int = mapper.invoke("androider")
return result
}
val result0:Int = stringMapper(mapper={
Log.e(TAG, "stringMapper 输出lambda参数it=$it") //stringMapper 输出lambda参数it=androider
val length = it.length
return@stringMapper length
})
Log.e(TAG, "输出结果result0=${result0}") //输出结果result0=9
案例5:
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
var result16: Int = all1.invoke(1, 2)
Log.e(TAG,"result16=$result16") // result16=3
var result17: Int = all2.invoke(2, 3)
Log.e(TAG,"result17=$result17") // result17=5
}
//匿名函数(函数字面值)
// 接收者对象Int类型, 申明一个变量 all1 all2 是个Int.(other:Int) -> Int 函数类型
// 接收者对象需要用户调用all1.invoke(1, 2)时进行传递
//this(隐士)=接收者对象
//函数体是:单表达式函数
val all1 : Int.(other:Int) -> Int = fun Int.(other: Int): Int = this + other
val all2 : Int.(other:Int) -> Int = fun Int.(other: Int) : Int {
Log.e(TAG,"接收者对象this=$this") //接收者对象this=2
return this + other
}
}
案例6:
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
var result18: Int = all3.invoke(3, 4)
Log.e(TAG,"result18=$result18") // result18=7
}
//lambda表达式(函数字面值)
// 接收者对象Int类型, 申明一个变量 all3 是个Int.(other:Int) -> Int 函数类型
// 接收者对象需要用户调用all3.invoke(1, 2)时进行传递
//this(隐士)=接收者对象
//函数体是:lambda表达式
val all3: Int.(other:Int) -> Int = { other ->
Log.e(TAG,"接收者对象this=$this") //接收者对象this=3
// 调用/访问 接收者对象this的成员
val result :Int = this.plus(other)
result //lambda最后一行作为返回值
}
}
案例7:
当可以从上下文推断接收者类型时,Lambda表达式可以作为函数字面量与接收者一起使用。
它们使用的一个最重要的例子是类型安全构建器 type-safe builders。
data class Student(val name: String="fmf", val age: Int=37){
fun body() {
Log.e("Student","Student is body")
}
override fun toString(): String {
return "student info name=$name,age=$age"
}
}
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
//调用者 调用study函数
// 获取接受者的lambda表达式 init={.......}
// this(隐士)=Student=接受者对象
val student:Student = study(init={ name:String,age:Int->
Log.e(TAG,"输出lambda表达式回调来的参数数值 name=$name, age =$age") //输出lambda表达式回调来的参数数值 name=liuLiang, age =33
var isMe:Boolean=false
if(name=="liuLiang"){
isMe=true
}
//调用 接收者对象this(隐士) = Student的方法body()
this.body() //输出 Student is body
isMe//lambda最后一行 isMe 最为返回值. 调用者把isMe数值 在传递 给接受者
}) //返回Student 类型
Log.e(TAG,"输出返回结果student=${student.toString()}") //输出返回结果student=student info name=liuLiang,age=33
/**
*输出lambda表达式回调来的参数数值 name=liuLiang, age =33
* 这个学生就是刘亮
* 输出返回结果student=student info name=liuLiang,age=33
* Student is body
* */
}
// 当可以从上下文推断接收者类型时,Lambda表达式可以作为函数字面量与接收者一起使用。
// 它们使用的一个最重要的例子是类型安全构建器 type-safe builders。
fun study(init:Student.(name:String,age:Int)->Boolean): Student {
//创建自定义接收器对象,就不是默认的this了
val student = Student(name="liuLiang",age=33)
// student.init(student.name,student.age)
//或者 这样写
val isMe:Boolean = init.invoke(student,student.name,student.age) //将name age 2个数值 传递给lambda表达式的2个参数
//接受者 接受 调用者 传递过来的数值结果 isMe 进行业务逻辑判断
if(isMe){
Log.e(TAG,"这个学生就是刘亮") //这个学生就是刘亮
}
return student //将接收器对象student 传递给lambda表达式里就生成了一个隐士对象this= Student
}
}
具名函数 -> 匿名函数 -> Lambda表达式写法的演变过程:
代码案例:
//具名函数(普通函数)
fun sum(x: Int, y: Int): Int = x + y
fun sum2(x: Int, y: Int): Int { return x + y }
// 只有变量才能传参使用,因此赋值给变量。aa是一个函数变量,它的类型是函数类型 (Int, Int) → Int,
//-> 左边是参数类型,右边是返回值类型。
val aa1:(Int, Int) ->Int = fun sum3(x: Int, y: Int): Int = x + y
val aa2:(Int, Int) ->Int = fun sum4(x: Int, y: Int): Int{ return x + y }
// 函数名字sum此时没有作用了,去掉函数名字sum就变成了匿名函数。
val aa3:(Int, Int) ->Int = fun(x: Int, y: Int): Int = x + y
val aa4:(Int, Int) ->Int = fun(x: Int, y: Int): Int{ return x + y }
//或者匿名函数( 前面不要加变量 )
fun(x: Int, y: Int): Int = x + y
fun (x: Int, y: Int): Int { return x + y}
fun(item:Int): Boolean = item > 2
fun(item:Int):Boolean { return item > 2}
//去掉fun关键字、去掉返回值类型Int、去掉参数列表的形参类型x: Int, y: Int, 进一步精简成Lambda表达式写法。
// Lambda表达式写法 {参数:类型->....}->左边是形参 右边是业务代码,函数体最后一行会被当作返回值。
val aa50:(Int, Int) ->Int = fun(x: Int, y: Int): Int = x + y
val aa51:(Int, Int) ->Int = { x:Int,y:Int-> x + y }
val aa52: (Int, Int) -> Int = { _, y -> y }
val aa53 = { x: Int, y: Int -> x + y }
val aa60:(Int, Int) ->Int = fun(x: Int, y: Int): Int{ return x + y }
val aa61:(Int, Int) ->Int = { x:Int,y:Int-> x + y }
val aa62: (Int, Int) -> Int = { _, y -> y }
val aa63 = { x: Int, y: Int -> x + y }
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
var result16: Int = all1.invoke(1, 2)
Log.e(TAG,"result16=$result16") // result16=3
var result17: Int = all2.invoke(2, 3)
Log.e(TAG,"result17=$result17") // result17=5
var result18: Int = all3.invoke(3, 4)
Log.e(TAG,"result18=$result18") // result18=7
}
//匿名函数(函数字面值)
// 接收者对象Int类型, 申明一个变量 all1 all2 是个Int.(other:Int) -> Int 函数类型
// 接收者对象需要用户调用all1.invoke(1, 2)时进行传递
//this(隐士)=接收者对象
//函数体是:单表达式函数
val all1 : Int.(other:Int) -> Int = fun Int.(other: Int): Int = this + other
val all2 : Int.(other:Int) -> Int = fun Int.(other: Int) : Int {
Log.e(TAG,"接收者对象this=$this") //接收者对象this=2
return this + other
}
//lambda表达式(函数字面值)
// 接收者对象Int类型, 申明一个变量 all3 是个Int.(other:Int) -> Int 函数类型
// 接收者对象需要用户调用all3.invoke(1, 2)时进行传递
//this(隐士)=接收者对象
//函数体是:lambda表达式
val all3: Int.(other:Int) -> Int = { other ->
Log.e(TAG,"接收者对象this=$this") //接收者对象this=3
// 调用/访问 接收者对象this的成员
val result :Int = this.plus(other)
result //lambda最后一行作为返回值
}
}
android中控件的点击事件的演变案例:
//匿名函数
textView.setOnClickListener(fun(v:View):Unit{
Log.e(TAG,"textView被点击了1")
})
//Lambda
textView.setOnClickListener({v:View->
Log.e(TAG,"textView被点击了2")
})
//Lambda参数类型能被推导可以省略
textView.setOnClickListener({v->
Log.e(TAG,"textView被点击了3")
})
//Lambda是函数最后一个参数可以写到小括号外面
textView.setOnClickListener() { v -> Log.e(TAG,"textView被点击了4") }
//Lambda是函数唯一参数可以去掉小括号
textView.setOnClickListener { v -> Log.e(TAG,"textView被点击了5") }
//Lambda只有一个参数可以省去不写,调用有默认名称 it
textView.setOnClickListener {
Log.e(TAG,"textView被点击了6")
}
内联函数
1.内联函数是什么
inline翻译成中文的意思就是内联,在kotlin里面inline被用来修饰(高阶)函数,表明当前函数在编译时是以内嵌的形式进行编译的,从而减少了一层函数调用栈。
当有人调用高阶函数时,我们无法知道调用者会在什么场景以及时机去调用高阶函数,一旦出现上述重复创建大量高阶函数对象的场景那么就会有严重的性能问题,而且这也是kotlin高阶函数的一个性能隐患。所以,基于这个问题kotlin提供了inline关键字来解决。
意指:当编译器发现某段代码在调用一个内联函数时,它不是去调用该函数,而是将该函数的代码,整段插入到当前位置。这样做的好处是省去了调用函数的过程,加快程序运行速度。(函数的调用过程,由于有前面所说的参数入栈等操作,所以总要多占用一些时间)。
使用内联函数的好处:省去了代码调用的过程,加快程序运行速度。
使用内联函数的不好处:由于每当代码调用到内联函数,就需要在调用处直接插入一段该函数的代码,所以程序的体积将增大。
内联函数的本质是,节省时间但是消耗空间
⒉.内联函数格式
⑴inline 函数的声明或定义(在函数声明或定义前加一个 inline 修饰符)
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
//调用内联函数max
max(1,2)
//实际编译运行的代码,并没有调用函数max(a:Int,b:Int)
// if(a>b) a else b
}
inline fun max(a:Int,b:Int):Int{
return if(a>b) a else b
}
}
这样写的一点好处就是调用栈会明显变浅,减少了一个函数max的调用
但是这个好处对应用程序的优化影响非常小,几乎可以忽略不计。甚至可能会由于多处调用代码重复编译导致编译字节码膨胀从而造成包体积变大的问题,这就得不偿失。所以inline关键字的正确使用场景并不是如上图所示。
⑵inline函数的规则
①一个函数可以自已调用自已,称为递归调用(后面讲到),含有递归调用的函数不能设置为inline;
②使用了复杂流程控制语句:循环语句和switch语句,无法设置为inline;
③由于inline增加体积的特性,所以建议inline函数内的代码应很短小。最好不超过5行。
④inline仅做为一种“请求”,特定的情况下,编译器将不理会inline关键字,而强制让函数成为普通函数。出现这种情况,编译器会给出警告消息。
⑤在你调用一个内联函数之前,这个函数一定要在之前有声明或已定义为inline,如果在前面声明为普通函数,而在调用代码后面才定义为一个inline函数,程序可以通过编译,但该函数没有实现inline。
⑥为了调试方便,在程序处于调试阶段时,所有内联函数都不被实现
⑶ 内联函数时应注意以下几个问题
①在一个文件中定义的内联函数不能在另一个文件中使用。它们通常放在头文件中共享。
②内联函数应该简洁,只有几个语句,如果语句较多,不适合于定义为内联函数。
③内联函数体中,不能有循环语句、if语句或switch语句,否则,函数定义时即使有inline关键字,编译器也会把该函数作为非内联函数处理。
④内联函数要在函数被调用之前声明。关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。
⒊内联inline修饰高阶函数(内联化 Lambda )
当有人调用高阶函数时,我们无法知道调用者会在什么场景以及时机去调用高阶函数,一旦出现上述重复创建大量高阶函数对象的场景那么就会有严重的性能问题,而且这也是kotlin高阶函数的一个性能隐患。所以,基于这个问题kotlin提供了inline关键字来解决。
inline关键字可以将函数体内部的代码内联到调用处,甚至还可以将高阶函数体内部的内部的代码也内联过去,而这个内部的内部的指的就是高阶函数内部的函数类型的参数
高阶函数中使用内联函数的原因:Lambda 表达式在底层被转换成了匿名类的实现方式。这就表明,我们每调用一次 Lambda 表达式,都会创建一个新的匿名类实例,当然也会造成额外的内存和性能开销。为了解决这个问题,Kotlin 提供了内联函数的功能,它可以将使用 Lambda 表达式带来的运行时开销完全消除,只需要在定义高阶函数时加上 inline 关键字的声明即可.
使⽤⾼阶函数会带来某些运⾏时的性能损耗,因为每⼀个函数都是⼀个对象且会捕获⼀个闭包。 即在函数体内访问到的那些变量时,因函数对象和类而进行的内存分配以及虚拟调⽤会引⼊运⾏时都会造成时间开销。
一般可以通过内联化 Lambda 表达式可以消除这类的开销,所以内联也是一种性能优化的手段。
案例1:
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
//for循环调用1000次testInline高阶函数
for (i in 0..1000) {
val testInlinePerson:Person = testInline(block = { str: String, num: Int ->
Log.e(TAG, "$i str=$str,num=$num") //str=fmf,num=22
val result: Int = str.length.plus(num)
result
})
Log.e(TAG, "$i 输出结果testInlinePerson=${testInlinePerson}") // 输出结果testInlinePerson=name:上帝, age:0, sex:0
}
/** 实际编译执行的代码
* for (i in 0..1000) {
* Log.e(TAG,"result=$result")
* val person =Person()
* Log.e(TAG, "$i str=$str,num=$num")
* Log.e(TAG, "$i 输出结果testInlinePerson=${testInlinePerson}")
* }
*
*
* */
}
// 我们使用一个关键字inline 修饰符来告诉编译器我这个函数testInline是内联函数,
inline fun testInline( block:(String,Int)->Int):Person{
val result:Int = block.invoke("fmf", 22)
Log.e(TAG,"result=$result") //result=25
val person =Person()
return person
}
}
这样就避免了函数类型的参数所造成的临时函数对象的创建,我们就可以在界面高频刷新、大量循环的场景下放心调用高阶函数了。
总的来说,inline关键字让函数以内联的方式进行编译避免创建函数对象来处理kotlin高阶函数的天然性能缺陷。同时,之前的文章中提到的kotlin的泛型实化,也是利用了inline关键字可以内嵌函数代码的特性而衍生出来的全新功能。
4.noinline 禁止内联
noinline的意思就是不内联,这个关键字只能作用于内联inline高阶函数的某个函数类型的参数上,表明当前的内联高阶函数的 参数 不参与高阶函数的内联。
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
noinlineTest1(
doSomething1 = {
Log.e(TAG, "执行了代码 2")
},
doSomething2 = {
Log.e(TAG, "执行了代码 3")
})
/** 打印日志:
* 执行了代码 1
* 执行了代码 2
* 执行了代码 3
* */
//实际编译执行的代码
/**
* Log.e(TAG, "执行了代码 1")
* Log.e(TAG, "执行了代码 2")
* ({ Log.e(TAG, "执行了代码 3")}).invoke()
* */
}
// 高阶函数 noinlineTest 返回值类型Unit
private inline fun noinlineTest1(doSomething1:()->Unit, noinline doSomething2:()->Unit):Unit{
Log.e(TAG, "执行了代码 1")
doSomething1.invoke()
doSomething2.invoke()
}
}
5.把 内联高阶函数的某一个 函数类型参数block:(Int)->Unit 作为内联高阶函数的返回值类型
这个noinline关键字有什么用呢?
在kotlin中高阶函数的函数类型的参数我们可以直接当做一个函数去调用,但是函数类型的参数终究还是一个对象,既然是一个对象那么我们就可以以对象的形式去使用,就比如说作为函数返回值进行返回
案例1:失败案例
把内联高阶函数 noinlineTest2 的函数类型参数 doSomething2:(Int)->Unit 当做一个对象 作为高阶函数noinlineTest2的返回值类型
结果编译报错:
因为作为内联函数的函数类型参数doSomething2,它已经不能作为对象去使用了。
说明doSomething2只是一个函数体而不是一个函数对象,所以doSomething2无法被当做一个对象那样作为返回值进行返回。
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
/**
* 把内联高阶函数 noinlineTest2 的函数类型参数 doSomething2:(Int)->Unit 当做一个对象 作为高阶函数noinlineTest2的返回值类型
* 结果编译报错
* 因为作为内联函数的函数类型参数doSomething2,它已经不能作为对象去使用了。
* 说明doSomething2只是一个函数体而不是一个函数对象,所以doSomething2无法被当做一个对象那样作为返回值进行返回。
* */
private inline fun noinlineTest2(doSomething1:(String)->Unit, doSomething2:(Int)->Unit ) : (Int)->Unit{
Log.e(TAG, "执行了代码 1")
doSomething1.invoke("2")
doSomething2.invoke(3)
//这里编译器会提示报错,因为作为内联函数的函数类型参数doSomething2,它已经不能作为对象去使用了。
//说明doSomething2只是一个函数体而不是一个函数对象,所以doSomething2无法被当做一个对象那样作为返回值进行返回。
return doSomething2 //todo err
}
}
}
案例2:改进方案1
去掉内联高阶函数 noinlineTest21 的inline 关键字
其的函数类型参数 doSomething2就是一个函数对象,就可以作为高阶函数 noinlineTest21 的返回值类型了
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val noinlineTest21Result: (Int)->Unit = noinlineTest21(
doSomething1 = {
Log.e(TAG, "noinlineTest21 it=$it 执行了代码$it")
},
doSomething2 = {
Log.e(TAG, "noinlineTest21 it=$it 执行了代码$it")
})
/** 打印日志:
* noinlineTest21 执行了代码 1
* noinlineTest21 it=2 执行了代码2
* noinlineTest21 it=3 执行了代码3
* */
}
//改进方案1: 去掉内联高阶函数 noinlineTest21 的inline 关键字 其的函数类型参数 doSomething2就是一个函数对象,就可以作为高阶函数 noinlineTest21 的返回值类型了
private fun noinlineTest21(doSomething1:(String)->Unit, doSomething2:(Int)->Unit ) : (Int)->Unit{
Log.e(TAG, "noinlineTest21 执行了代码 1")
doSomething1.invoke("2")
doSomething2.invoke(3)
return doSomething2 //todo ok
}
}
案例3:改进方案2
给内联高阶函数 noinlineTest2 的函数类型参数 doSomething2:(Int)->Unit 添加noinline关键字
我们就可以这个个函数类型的参数doSomething2 作为对象去使用了,就可以作为内联高阶函数 noinlineTest21 的返回值类型了
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val noinlineTest22Result: (Int)->Unit = noinlineTest22(
doSomething1 = {
Log.e(TAG, "noinlineTest22 it=$it 执行了代码$it")
},
doSomething2 = {
Log.e(TAG, "noinlineTest22 it=$it 执行了代码$it")
})
/** 打印日志:
* noinlineTest22 执行了代码 1
* noinlineTest22 it=2 执行了代码2
* noinlineTest22 it=3 执行了代码3
*
* */
}
//改进方案2: 给内联高阶函数 noinlineTest2 的函数类型参数 doSomething2:(Int)->Unit 添加noinline关键字
//我们就可以这个个函数类型的参数doSomething2 作为对象去使用了,就可以作为内联高阶函数 noinlineTest21 的返回值类型了
private inline fun noinlineTest22(doSomething1:(String)->Unit, noinline doSomething2:(Int)->Unit):(Int)->Unit{
Log.e(TAG, "noinlineTest22 执行了代码 1")
doSomething1.invoke("2")
doSomething2.invoke(3)
return doSomething2 //todo ok
}
}
6.crossinline
crossinline的含义字面上可以理解为对inline做局部加强内联
案例1:内联函数crossinlineTest1 的函数类型参数doSomething1:(String)->Unit 使用lambda表达式具体实现的作用域内没有return
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
// 内联函数crossinlineTest1 的函数类型参数doSomething1:(String)->Unit 使用lambda表达式具体实现的作用域内没有return
crossinlineTest1(doSomething1 = {
Log.e(TAG, "crossinlineTest1 执行了代码$it")
//return
})
Log.e(TAG, "crossinlineTest1 执行了代码3")
/** 打印日志:
* crossinlineTest1 执行了代码 1
* crossinlineTest1 执行了代码 2
* crossinlineTest1 执行了代码 3
*
* */
//实际编译的代码执行顺序
/**
* Log.e(TAG, "crossinlineTest1 执行了代码 1")
* Log.e(TAG, "crossinlineTest1 执行了代码2")
* Log.e(TAG, "crossinlineTest1 执行了代码3")
* */
}
private inline fun crossinlineTest1(doSomething1:(String)->Unit):Unit{
Log.e(TAG, "crossinlineTest1 执行了代码 1")
doSomething1.invoke("2")
}
}
案例2: 内联函数crossinlineTest2 的函数类型参数doSomething1:(String)->Unit 使用lambda表达式具体实现的作用域内使用return 但是最外面fun onCreate()函数的代码逻辑就不执行了
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
// 内联函数crossinlineTest2 的函数类型参数doSomething1:(String)->Unit 使用lambda表达式具体实现的作用域内使用return 但是外面下面的代码逻辑不执行了
crossinlineTest2(doSomething1 = {
Log.e(TAG, "crossinlineTest2 执行了代码$it")
return //todo 这里的return结束的是crossinlineTest2内联函数体中的代码逻辑, crossinlineTest2内联函数体外下面的其他代码逻辑会被执行
// 但是 crossinlineTest2内联函数体外下面的其他代码 Log.e(TAG, "crossinlineTest2 执行了代码3") 并没有被执行打印输出
// 这是因为内联函数crossinlineTest2会在编译时被完全铺平,这里return结束的就是最外面 fun onCreate()函数的代码逻辑, 所以下面的代码 Log.e(TAG, "crossinlineTest2 执行了代码3") 不会被执行打印输出
})
Log.e(TAG, "crossinlineTest2 执行了代码3")
/**
* 打印日志:
* crossinlineTest2 执行了代码 1
* crossinlineTest2 执行了代码2
* //todo crossinlineTest1 执行了代码 3 日志不会输出
* */
//实际编译的代码执行顺序
/**
* Log.e(TAG, "crossinlineTest1 执行了代码 1")
* Log.e(TAG, "crossinlineTest1 执行了代码2")
* return 全面结束 不会执行下面的代码逻辑
//todo Log.e(TAG, "crossinlineTest1 执行了代码3") 代码不会执行
* */
}
private inline fun crossinlineTest2(doSomething1:(String)->Unit):Unit{
Log.e(TAG, "crossinlineTest2 执行了代码 1")
doSomething1.invoke("2")
}
}
案例3:解决方案1:非内联高阶函数的函数类型参数的具体实现lambda表达式内要指定具体的return@label标签
kotlin提出了一个新规定:普通非内联高阶函数noInLineFun的lambda表达式函数体内不允许直接return,会报错,因为高阶函数noInLineFun不是内联高阶函数,return结束的是最外层的函数fun onCreate()代码逻辑。
通过return@label的形式来显示指定return结束的代码作用域就是高阶函数noInLineFun的lambda表达式函数体内,结束的不是最外面的函数fun onCreate()代码逻辑。这样 最外面的下面代码逻辑也会被执行
class HigherOrderFunctionSuspendActivity2 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
noInLineFun(doSomething1 = {
Log.e(TAG, "noInLineFun 执行了代码$it")
//return //todo 普通非内联高阶函数noInLineFun的lambda表达式函数体内不允许直接return,会报错,因为高阶函数noInLineFun不是内联高阶函数,return结束的是最外层的函数fun onCreate()代码逻辑。
return@noInLineFun //todo 通过return@label的形式来显示指定return结束的代码作用域就是高阶函数noInLineFun的lambda表达式函数体内,结束的不是最外面的函数fun onCreate()代码逻辑。这样 最外面的下面代码逻辑也会被执行
})
Log.e(TAG, "noInLineFun 执行了代码3")
/** 打印输出日志
* noInLineFun 执行了代码 1
* noInLineFun 执行了代码2
* noInLineFun 执行了代码3
* */
}
private fun noInLineFun(doSomething1:(String)->Unit):Unit{
Log.e(TAG, "noInLineFun 执行了代码 1")
doSomething1.invoke("2")
}
}
案例4:解决方案2: 内联高阶函数添加 crossinline 关键字修饰 并且其的类型参数doSomething1的具体实现lambda表达式内要指定具体的return@label标签
错误报错的代码:
class HigherOrderFunctionSuspendActivity2 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
crossinlineTest3(doSomething1={
Log.e(TAG, "crossinlineTest3 执行了代码$it")
return //todo 这里会编译报错 解决方案: return@crossinlineTest3
})
Log.e(TAG, "crossinlineTest3 执行了代码3")
}
private inline fun crossinlineTest3(doSomething1:(String)->Unit):Unit{
Log.e(TAG, "crossinlineTest3 执行了代码 1")
Runnable {
doSomething1.invoke("2") //todo 这里会编译报错 解决方案:给 doSomething1添加 crossinline
}
}
}
正确解决方案代码:
什么叫间接调用的关系?
内联函数crossinlineTest3中将函数类型参数doSomething1放到了子线程Runnable{}里面去执行, 那么doSomething1和crossinlineTest3就属于间接调用的关系。
间接调用的问题
在调用 内联高阶函数crossinlineTest3的函数类型参数doSomething1的具体实现lambda表达式内如果要return结束的到底是Runnable子线程中的逻辑还是最外层的函数fun onCreate()逻辑呢?不知道 就会报错 不允许这样写
解决方案:
kotlin为此又新增了一条规定:内联函数crossinlineTest3中不允许类似上述问题中对函数类型参数doSomething1的间接调用,
解决方案就是给函数类型参数 doSomething1 添加 crossinline 关键字修饰 这就是局部加强内联
crossinline可以到达间接调用函数类型参数doSomething1的目的
class HigherOrderFunctionSuspendActivity2 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
crossinlineTest32(
doSomething1= {
Log.e(TAG, "crossinlineTest32 执行了代码$it❷")
return@crossinlineTest32 //todo ok
})
Log.e(TAG, "crossinlineTest32 执行了代码3")
/** 打印输出日志
* crossinlineTest32 执行了代码 1
* crossinlineTest32 执行了代码3
* crossinlineTest32 执行了代码 2❶
* crossinlineTest32 执行了代码2❷
* */
}
private inline fun crossinlineTest32(crossinline doSomething1:(String)->Unit):Unit{
Log.e(TAG, "crossinlineTest32 执行了代码 1")
val runnable = Runnable {
Log.e(TAG, "crossinlineTest32 执行了代码 2❶")
doSomething1.invoke("2") //todo ok
}
findViewById<TextView>(R.id.textview).post(runnable)
}
}
Function接口
针对函数对象的行为(传参、处理、返回)抽象出接口。Kotlin 定义了 kotlin.Function<out R> 接口来抽象所有的函数,它没有定义任何方法。kotlin.jvm.functions 包里定义了 Function0<out R> 到 Function22<out R> 来分别抽象无参到 22 个参数的函数,它们都继承了 kotlin.Function 接口,同时定义了一个 invoke() 函数。invoke() 函数定义了“调用”这个行为,它同时重载了括号操作符“()”,允许我们用括号来传入参数、得到返回值。
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
var result19:Int = sum.invoke(1, 2)
Log.e(TAG,"result19=$result19") //result19=3
var result20:Int = sum(2, 3)
Log.e(TAG,"result20=$result20") //result20=5
}
//例如Function2接口
//P1、P2是传入的两个参数的类型,R是返回值类型
//因为我们只会向函数中传入参数取出返回值,所以用in out收窄功能
interface Function2<in P1, in P2, out R> : Function<R> {
operator fun invoke(p1: P1, p2: P2): R
}
//对于我们写的sum函数对象
//会变编译成Function2类型的对象,(Int, Int) -> Int 是具体实现类
//这时我们就可以用invoke()来调用它
val sum: (Int, Int) -> Int = {a, b -> a + b}
}
invoke约定
函数类型的值可以通过使用其 invoke(...) 操作符来调用,invoke() 表示通过 函数变量 调用自身。
❶我们只需要在一个类中使用 operator 来修饰 invoke() 函数,这样的类的对象就可以直接像一个保存 lambda 表达式的变量一样直接调用,而调用后执行的函数就是invoke函数。(把对象当作函数名一样调用函数)
❷还有另一种方式来实现可调用的对象,即让类继承自函数类型,然后重写 invoke() 方法.
❸让函数变量像 对象调用函数 一样调用自身。
package com.my.runalone_coroutinedemo
import android.util.Log
class A(val str: String) {
val TAG="HigherOrderFunctionSuspendActivity"
operator fun invoke() {
Log.e(TAG,str)
}
}
package com.my.runalone_coroutinedemo
class B:(String,Int)->String {
override fun invoke(str: String, p2: Int): String {
return str+p2
}
}
val a = A("Hello")
a() // Hello
//等价于 a.invoke() //Hello
val bB = B()
val result26:String = bB.invoke("kotlin", 15) //等价于 b("kotlin", 15)
Log.e(TAG,"result26=$result26") //result26=kotlin15
SAM转换
Java 不支持函数编程(函数只能存在于类中),变通方案是接口回调(函数式接口)。抽象方法一样是定义了参数列表和返回值类型(就像Kotlin中的Lambda,多了无卵用的函数名),只是外层套了一个接口(没有函数类型的对象用来存储传递,实在需要写个类实现后创建对象),都是等实际使用的时候根据情况再编写具体内容,通过匿名内部类对象创建(Java的Lambda只是对匿名内部类简写的语法糖),而这就是SAM转换。
Kotlin调用Java中定义的方法(带有Java的SAM参数)时:同样支持SAM转换。底层是将Java的SAM翻译成函数类型。
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
//调用
eat(type=5, cutlery = {name: String, num: Int->
Log.e(TAG,"name=$name, num=$num") //name=汤匙, num=100
"餐具"//lambda最后一行作为返回值
})
}
// Kotlin调用Java中定义的方法(带有Java的SAM参数)时:同样支持SAM转换。底层是将Java的SAM翻译成函数类型。
//底层是将Java中的eat()函数翻译成koltin的函数类型
//Java中的eat()函数
// public void eat(int type, Cutlery cutlery) {}
//koltin的eat()函数 第二个参数使用 函数类型表示
fun eat(type: Int, cutlery: (name: String, num: Int) -> String):Unit{
var result:String = cutlery.invoke("汤匙", 100)
Log.e(TAG,"输出result=$result") //输出result=餐具
}
}
Kotlin调用Kotlin中定义的方法(带有Java的SAM参数)时:可以使用SAM构造和匿名内部类的方式调用。由于该方法是在Kotlin中明确定义的,因此它不是一个函数类型无法使用Lambda调用。SAM构造相对于匿名内部类创建的好处是,除了书写简便,在不使用外部变量的情况下SAM会保持一个静态对象的引用而不是每次创建对象,性能会好点。
Cutlery是Java 接口
public interface Cutlery {
String getCutlery(String name,int num);
}
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
//todo Cutlery是Java 接口
//使用SAM构造调用(推荐)
eat2(type=5, cutlery = Cutlery { name, num ->
Log.e(TAG,"eat2 name=$name, num=$num") //eat2 name=大碗, num=101
"餐具二套"//lambda最后一行最为返回值
})
//使用匿名内部类调用
eat2(type=5,cutlery = object :Cutlery{
override fun getCutlery(name: String?, num: Int): String {
Log.e(TAG,"eat2 name=$name, num=$num") //eat2 name=大碗, num=101
return "餐具三套"
}
})
}
private fun eat2(type:Int, cutlery: Cutlery):Unit {
val cutlery1 = cutlery.getCutlery("大碗", 101)
Log.e(TAG,"输出 cutlery1 =$cutlery1") //输出 cutlery1 =餐具二套 输出 cutlery1 =餐具三套
}
}
Kotlin调用Kotlin中定义的方法(带有Kotlin的SAM参数)时:只能使用匿名内部类方式调用。既然Kotlin支持函数编程,就不希望你用Java这种SAM编程。
Cutlery2是Kotlin 接口
interface Cutlery2 {
fun getCutlery(name: String?, num: Int): String?
}
class HigherOrderFunctionSuspendActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
//todo Cutlery2是Kotlin 接口
//只能使用匿名内部类调用
eat3(type=5,cutlery=object :Cutlery2{
override fun getCutlery(name: String?, num: Int): String? {
Log.e(TAG,"eat3 name=$name, num=$num") //eat3 name=盘子, num=102
return "餐具四套"
}
})
}
private fun eat3(type: Int, cutlery: Cutlery2):Unit{
val cutlery2 = cutlery.getCutlery("盘子", 102)
Log.e(TAG,"输出 cutlery2 =$cutlery2") //输出 cutlery2 =餐具四套
}
}
闭包
闭包的演变由来:
我们先以 Thread
为例,来看看什么是闭包:
/**
在这里需要一个类型为 Runnable 的参数,而 Runnable 是一个接口,
且只定义了一个函数 run,这种情况满足了 Kotlin 的 SAM,
可以转换成传递一个 lambda 表达式(第二段),
因为是最后一个参数,根据闭包原则我们就可以直接写成 Thread {...}(第三段) 的形式。
*/
// 创建一个 Thread 的完整写法
Thread(object : Runnable {
override fun run() {
...
}
})
// 满足 SAM,先简化为
Thread({
...
})
// 使用闭包,再简化为
Thread {
...
}
形如
Thread {...}
这样的结构中{}
就是一个闭包。在 Kotlin 中有这样一个语法糖:当函数的最后一个参数是 lambda 表达式时,可以将 lambda 写在括号外。这就是它的闭包原则。
闭包函数
函数(具名函数、匿名函数、Lambda)可以捕获(引用和修改)自己外部函数中的局部变量, 会使外部变量保存在内存中避免其随着外部环境的销毁,保证了变量的安全性但有性能开销。闭包赋值给变量后,变量销毁内存释放。
1 Java
Java中函数不是一等公民无法嵌套声明,函数必须存在于类/接口中,使用的是函数式接口解决方案。匿名内部类类似于一个闭包,当捕捉外部的final变量时,它的值和使用这个值的Lambda代码(匿名内部类)一起存储。而对于非final变量来说,因为对外部的局部变量引用和修改无法阻止该变量随着外部环境运行结束而一起销毁(栈帧出栈)。解决办法是变量的存储位置由栈转堆,具体实现是外部定义长度为1的数组用arr[0]操作变量,原理是Java会为数组中的元素在堆内存中开辟连续的空间,这样该变量就会因为被引用而不被回收(GC可达性),通过持有该变量的引用来使得内外可以修改同一个变量。但我们一般都是操作类的成员变量,而不是受到栈帧出栈影响的外部函数中的局部变量。
//经典案例
void onCreate(Bundle savedInstanceState) {
//这里是外部环境
int num = 0;
btn.setOnClickListener(v -> {
//这个内部的函数里引用了外部变量(匿名内部类的函数)
num++; //报错:num必须声明为final
});
}
//解决办法
void onCreate(Bundle savedInstanceState) {
int[] num = new int[]{1};
btn.setOnClickListener(v -> {
num[0]++;
});
}
2 Kotlin
Kotlin可以修改闭包中捕获的变量。即Lambda 表达式或者匿名函数可以访问修改外部作用域中声明的变量
Kotlin的捕捉只不过把 Java 中的一些实现细节给优化了,比如捕捉 val 变量时,它的值会被拷贝下来,当捕捉 var 变量时,它的值会被作为 Ref 类的一个实例被保存下来。
案例:
override fun onCreate(savedInstanceState: Bundle?){
var num=1
findViewById<Button>(R.id.button).setOnClickListener {
num++ //访问修改外部局部变量num的值
Log.e(TAG,"num=$num")
}
/**
* num=2
* num=3
* num=4
* num=5
* num=6
* */
}
//反编译后的java代码
//被闭包引用的 int 局部变量,会被封装成 IntRef 这个类。
//这个 IntRef 里面保存着 int 变量,原函数和闭包都可以通过 intRef 来读写 int 变量。
//Kotlin 正是通过这种办法使得局部变量可修改。除了 IntRef,还有 LongRef,FloatRef 等
//如果是非基础类型,就统一用 ObjectRef 即可。
onCreate() {
final Ref.IntRef num = new Ref.IntRef();
num.element = 0;
btn.setOnClickListener(v -> {
num.element++;
});
}
案例:
//闭包函数 就是fun()函数作为aaa()函数的返回参数 (Int)
fun aaa(): () -> (Int) {
var current = 10
return fun(): Int {
return current++
}
//闭包函数 就是fun()函数作为bbb()函数的返回参数 (String)
fun bbb(): (String) -> (String) {
var current = 0;
return fun(str: String): String {
current++;
return "$str --- $current";
}
扩展函数和扩展属性
Kotlin通过使用一个特殊声明来扩展⼀个类的新功能⽽⽆需继承该类或使⽤像装饰者这样的任何类型的设计模式,主要体现为扩展函数 和 扩展属性。
1.扩展函数
声明⼀个扩展函数,我们需要⽤⼀个 接收者类型 (即被扩展的类型)来作为他的前缀,再跟上扩展函数的名称。
接收者类型(被扩展的类型) .函数{
在扩展函数swap体内部的this 就是接收者类型对象(即传过来的在 . 点符号前的对象)
this
}
class HigherOrderFunctionSuspendActivity2 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val list:MutableList<Int> = mutableListOf(1,2,3)
for (i in list) {
Log.e(TAG,"前 list元素:$i") // 前 list元素:1 2 3
}
list.swap(index1=0,index2=2)
for (i in list) {
Log.e(TAG,"后 list元素:$i") // 后 list元素:3 2 1
}
}
fun MutableList<Int>.swap(index1: Int, index2: Int){
// 在扩展函数swap体内部的this 就是接收者类型对象(即传过来的在 . 点符号前的对象)
//this=list:MutableList<Int>
Log.e(TAG, "输出接受者类型对象this=${this.toString()}") //输出接受者类型对象this=[1, 2, 3]
val tmp = this[index1]
this[index1]=this[index2]
this[index2]=tmp
}
}
⑴扩展函数也是支持泛型的,对应的任何类型的 MutableList 都起作⽤,我们可以在接收者类型表达式中使⽤泛型,只需要在函数名前声明泛型参数
fun <T> MutableList<T>.swap2(index1: Int, index2: Int){
// 在扩展函数swap体内部的this 就是接收者类型对象(即传过来的在 . 点符号前的对象)
//this=list:MutableList<Int>
Log.e(TAG, "输出接受者类型对象this=${this.toString()}") //输出接受者类型对象this=[1, 2, 3]
val tmp = this[index1]
this[index1]=this[index2]
this[index2]=tmp
}
⑵ 扩展是静态解析的,扩展并没有真正的修改他们所扩展的类,也没有在⼀个类中插⼊新成员,仅仅是可以通过该类型的变量⽤点表达式去调⽤这个新函数。
❶扩展函数是静态分发的,他们不是根据接收者类型的虚⽅法。即调⽤的扩展函数是由函数调⽤所在的表达式的类型来决定的, ⽽不是由表达式运⾏时求值结果决定的。例如:
class HigherOrderFunctionSuspendActivity2 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val c:C =C()
printFoo(c) // 输出结果=c
val d:D = D()
printFoo(d) // todo 输出结果=c // 因为调⽤的扩展函数只取决于参数 c 的声明类型c.foo(), 该类型是 C 类
}
open class C
class D:C()
//定义类型C的扩展函数foo()
fun C.foo():String{
Log.e(TAG, "C 输出接受者类型对象this=${this}") //C 输出接受者类型对象this= HigherOrderFunctionSuspendActivity2$C@e42864f
return "c"
}
//定义类型D的扩展函数foo()
fun D.foo():String{
Log.e(TAG, "D 输出接受者类型对象this=${this}")
return "d"
}
private fun printFoo(c:C){
Log.e(TAG,"输出结果="+c.foo())
}
}
❷如果⼀个类定义有⼀个成员函数和⼀个扩展函数且这两个函数⼜有相同的接收者类型、相同的名字并且都适⽤给定的参数,这种情况总是取成员函数,
class HigherOrderFunctionSuspendActivity2 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val e:E = E()
// 这里会调用成员函数foo() 不会调用扩展函数 E.foo()
e.foo() // 输出E类的成员函数 foo()
}
class E{
val TAG="HigherOrderFunctionSuspendActivity2"
//定义E类的成员函数foo()
fun foo(){
Log.e(TAG, "输出E类的成员函数 foo()")
}
}
//定义类型E的扩展函数foo()
fun E.foo(){
Log.e(TAG, "输出E类的扩展函数 E.foo()")
}
}
⑶为可空的接收者类型定义扩展函数
可空类型的扩展函数可以在对象变量上直接调⽤, 即使其值为 null,并且可以在扩展函数体内检测 this == null ,这能让你在没有检测 null 的时候调⽤ Kotlin 中的toString():检测发⽣在扩展函数的内部。
class HigherOrderFunctionSuspendActivity2 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
var str:String?=null
val result:String = str.toString()
Log.e(TAG, "输出result=$result") // 输出result=null
str="andrio"
val result2:String = str.toString()
Log.e(TAG, "输出result2=$result2") // 输出result2=andrio
}
private fun Any?.toString():String{
//this=Any? 调用者对象 可能为null
if(this==null){
return "null"
}//else{
return this.toString()
// }
}
}
2.扩展属性
Kotlin 的扩展属性和扩展函数差不多,例如为List添加一个名为lastIndex的扩展属性,由于扩展没有实际的将成员插⼊类中,因此对扩展属性来说后备字段是⽆效的。所以扩展属性不能有初始化操作。他们的⾏为只能由显式提供的 getters/setters 定义。
class HigherOrderFunctionSuspendActivity2 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val lastIndex = list.lastIndex
Log.e(TAG, "输出lastIndex=$lastIndex") //输出lastIndex=2
}
//为List<T>类型添加一个名为lastIndex的扩展属性 不能初始化 只能通过get(){}初始化 获取扩展属性值
val <T> List<T>.lastIndex:Int
get() {
return this.size-1
}
}
3.为⼀个类定义的⼀个伴⽣对象 ,也可以为这个伴⽣对象定义扩展函数和扩展属性。
class HigherOrderFunctionSuspendActivity2 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
//调用MyClass类伴生对象 Companion的扩展函数
MyClass.Companion.foo("1") // 输出MyClass类的伴生对象Companion的扩展函数foo()结果=1
//调用MyClass类伴生对象 Companion的成员函数
MyClass.Companion.myFoo() // 输出MyClass类的伴生对象Companion的成员函数myFoo()
}
class MyClass{
// 为MyClass类定义伴生对象 通过"Companion"表示伴生对象
companion object{
val TAG="HigherOrderFunctionSuspendActivity2"
//为伴生对象定义一个成员函数
fun myFoo(){
Log.e(TAG, "输出MyClass类的伴生对象Companion的成员函数myFoo()")
}
}
}
// 为MyClass类定义伴生对象Companion定义个扩展函数foo
fun MyClass.Companion.foo(str:String){
Log.e(TAG, "输出MyClass类的伴生对象Companion的扩展函数foo()结果=$str")
}
}
4.在⼀个G类内部你可以为另⼀个F类声明扩展(函数)。在这样的扩展(函数)内部,就会有多个 隐式接收者。
扩展(函数)声明所在的G类的实例称为 分发接收者,扩展(函数)调⽤所在的接收者类型F的实例称为 扩展接收者 。
当分发接收者G和扩展接收者F的成员(函数)名字冲突(一样)的情况,扩展接收者F优先。要引⽤分发接收者G的成员(函数 )你可以使⽤ 限定的 this@分发接收者G.成员 进行调用。
class HigherOrderFunctionSuspendActivity2 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val g:G= G()
val f:F = F()
g.caller(f)
/**
* 输出当前调用者是对象this=HigherOrderFunctionSuspendActivity2$F@ff53a66
* 输出F类的成员函数bar()
* 输出G类的成员函数baz()
输出F类的成员函数tostring()
输出G类的成员函数tostring()
*/
}
class F{
val TAG="HigherOrderFunctionSuspendActivity2"
fun bar(){
Log.e(TAG, "输出F类的成员函数bar()")
}
fun tostring() {
Log.e(TAG, "输出F类的成员函数tostring()")
}
}
class G{
val TAG="HigherOrderFunctionSuspendActivity2"
fun baz(){
Log.e(TAG, "输出G类的成员函数baz()")
}
fun tostring() {
Log.e(TAG, "输出G类的成员函数tostring()")
}
//在G类里 为F类定义一个扩展函数foo()
fun F.foo(){
Log.e(TAG, "输出扩展接收者F对象this=$this") //输出当前调用者是对象this=HigherOrderFunctionSuspendActivity2$F@ff53a66
this.bar() //todo 调用F类的bar()
this@G.baz() //todo 调用G类的baz()
this.tostring() //todo 调用F类的tostring()
this@G.tostring() //todo 调用G类的tostring()
}
fun caller(f:F){
f.foo() //调用F类的扩展函数foo()
}
}
}
5.声明为成员的open 扩展(函数) 可以在⼦类被override覆盖实现重写。这意味着这些扩展(函数)的分发对于分发接收者类型C C1是虚拟的,但对于扩展接收者类型D 是静态的。
class HigherOrderFunctionSuspendActivity3 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?){
val c:C= C()
c.caller(D()) // 输出 D.foo in C
c.caller(D1()) // 输出 D.foo in C // 扩展接收者D静态解析
val c1:C1 =C1()
c1.caller(D()) // 输出 D.foo in C1
c1.caller(D1()) // 输出 D.foo in C1 //分发接收者C C1虚拟解析
}
open class D {}
class D1 : D() {}
open class C {
val TAG="HigherOrderFunctionSuspendActivity3"
//在C类里 为扩展接受者D定义一个扩展函数open foo()
open fun D.foo(){
Log.e(TAG,"D.foo in C")
}
//在C类里 为扩展接受者D1定义一个扩展函数open foo()
open fun D1.foo() {
Log.e(TAG,"D1.foo in C")
}
//在C类里 定义一个成员函数 caller
fun caller(d:D){
d.foo() //调用接受者对象D的扩展函数foo()
}
}
class C1 : C() {
//在C1类里 override 父类C类里定义的扩展接受者D定义一个扩展函数foo()
override fun D.foo() {
Log.e(TAG,"D.foo in C1")
}
//在C1类里 override 父类C类里定义的扩展接受者D1定义一个扩展函数foo()
override fun D1.foo() {
println("D1.foo in C1")
}
}
}
鸣谢
Kotlin 高阶函数Lambdas的高端用法_kotlin高阶函数好处-CSDN博客
Kotlin - Lambda表达式(匿名函数)_kotlin 匿名-CSDN博客
Kotlin - 作用域函数(apply、also、run、with、let)_kotlin let run apply with函数-CSDN博客
Kotlin的inline、noinline和crossinline关键字 内联_ninnlij-CSDN博客
Kotlin——程序的灵魂组成之Lambda表达式、匿名函数、高阶函数的基本语法(九)_一个带接收者类型的挂起函数-CSDN博客