Lambda表达式和匿名函数
一、函数类型
1.1、函数类型界说
Kotlin运用相似(Int) → String
的函数类型来处理函数的声明
声明规则:
-
函数类型都有一个圆括号括起来的参数类型列表以及一个回来类型:形如
(A, B) -> C
表示接受的类型分别为A
与B
两个参数并回来C
类型值的函数类型。参数类型列表可认为空,如:() -> C
。Unit 回来类型
不行省掉 -
函数类型能够有一个额定的接纳者类型,声明规则是在上述函数声明前面加
接纳者类型.
例如A.(B) -> C
。带接纳者的函数和一般函数的区别主要有如下两点- 除了能够和一般函数相同的办法调用(接纳者目标作为一般函数的第一个参数)外,还能够经过
接受者类型目标.(参数1,参数2,…)即A.(B)
的办法调用 - 在函数体内,传给调用的接受者目标成为隐式的
this
,以便拜访接纳者目标的成员而无需任何额定的限定符,亦可运用this
关键字拜访接受者目标
- 除了能够和一般函数相同的办法调用(接纳者目标作为一般函数的第一个参数)外,还能够经过
-
挂起函数属于函数类型的特别种类,它的声明中有一个
suspend
修饰符,例如suspend() → Unit
或许suspend A.(B) → C
函数类型声明能够挑选性地包含函数的参数名: (x:Int, y:Int) → Point
。这些名称能够用于表示参数的意义。
函数类型能够运用圆括号进行接合: (Int) → ((Int) → Unit)
等价于(Int) → (Int) → Unit
可是不等价于((Int) → Int) →Unit
。
1.2、函数类型实例化
函数字面值:指不声明而是直接作为表达式传递的函数,lambda表达式和匿名函数都是函数字面值
-
运用字面值的代码块,即以下办法之一
- lambda表达式:
{a, b → a+b }
- 匿名函数:
fun(a:Int, b:Int): Int { return a+b }
带接纳者类型的函数字面值可用作实例化带有接纳者的函数类型
- lambda表达式:
-
运用已有声明的可调用引证
- 顶层、局部、成员、扩展函数;示例:
::isOdd、String::toInt
- 顶层、成员、扩展属性:
List<Int>::size
(???这一点说实话没太理解
) - 结构函数:
::Regex
包含指向特定实例成员的绑定的可调用引证:
foo::toString
- 顶层、局部、成员、扩展函数;示例:
-
运用完成函数类型接口的自界说类的实例
//函数类型能够当成接口完成
class Sum : (Int, Int) -> Int {
override fun invoke(p1: Int, p2: Int): Int {
return p1 + p2
}
}
val sum: (Int, Int) -> Int = Sum()
//调用
val result = sum.invoke(2,2)
//或许当成函数调用
val result = sum(2,2)
带与不带接纳者的函数类型非字面值(不是将函数直接当成参数传递)能够互换,其中接纳者能够代替第一个参数,反之亦然。例如,(A, B) → C
类型的值能够赋值给期待A.(B) → C
类型值的地方,反之亦然
val double: (Int) -> Int = { x -> x * x }
val threeTimes: Int.() -> Int = { this * this * this }
fun calculate(x: Int, cal: (Int) -> Int): Int {
return cal(x)
}
fun calculate2(x: Int, cal: Int.() -> Int): Int {
return cal(x)
}
fun test(): Int {
val value1 = calculate(100, double) //OK value1=10000
val value2 = calculate2(100, double) //OK value2=10000
val value3 = calculate(100, threeTimes) //OK value3=1000000
val value4 = calculate2(100, threeTimes) //OK value4=1000000
//字面值是不行以互换的
//calculate(100, { this * this * this }) //build fail
//calculate2(100, { x -> x * x }) //build fail
}
默许情况下推断出的是没有接纳者的函数类型,即便变量是经过扩展函数引证来初始化的。假如需求接纳者办法调用,需求显式指定函数类型
//注意这个要写在顶层函数里,局部函数无法经过 类型::办法名的办法引证
fun Int.realNameDouble2(): Int {
return this * this
}
class xxx{
fun realNameDouble(x: Int): Int {
return x * x
}
val double2: Int.() -> Int = ::realNameDouble
val double3 = Int::realNameDouble2
fun test() {
100.double2()
100.double3() //编译错误,不能经过这种办法调用
double3(100)
100.realNameDouble2()
}
}
2、高阶函数
高阶函数是将函数用作参数或许回来值的函数
//示例:
fun calculate(x: Int, cal: (Int) -> Int): Int {
return cal(x)
}
上述示例,除了传入正常的参数x
,还有一个参数cal
它的类型便是函数类型(Int) -> Int
。因而calculate函数便是一个高阶函数,能够接纳一个函数类型的实例
作为参数
//调用示例
fun main() {
//传入一个lambda表达式作为函数实例
////假如函数参数在最终一个,lambda表达式作为参数传入时能够放在圆括号之外
calculate(100) { x -> x + x }
}
二、高阶函数运用
2.1、官方自带高阶函数剖析
高阶函数 let、apply、also、with 剖析
2.2、自界说高阶函数—多参数判空
咱们经常会遇到一个场景,需求同时对多个参数进行判空,任一参数为空则不持续履行
在Java中需求一个个参数判断,假如参数很多代码很长并且复杂,能够写一个多参数判空的Kotlin的高阶函数来简化该逻辑
inline fun <T, R> notNull(vararg args: T?, block: () -> R): R? {
val filter = args.filterNot { it == null }
val size = filter.size
if (filter.size == args.size) {
return block()
}
return null
}
//测验一下
var string1 = "1"
var string2 = "2"
fun main() {
notNull(string1, string2) {
//参数均不为空,持续履行
}
}
2.3、自界说高阶函数—替换接口
比方有这个一个场景,咱们需求经过标识查看有没有创立目标,没有创立目标时再去创立。
详细比方Fragment的创立,由于办法或许会被多地方调用创立不同的Fragment。
依照之前的写法咱们不想在不需求的时分的创立Fragment实例,要么只能分开在不同的办法中写相似的逻辑;要么只能经过接口回调的办法完成需求时再创立Fragment实例,将这个场景笼统的同一个办法中
可是经过高阶函数咱们能够直接经过一个办法完成,也无法新增接口;实例如下:
private fun showFragment(tag: String, create: () -> Fragment) {
val fragment = supportFragmentManager.findFragmentByTag(tag)
if (fragment?.isAdded == true) {
return
}
supportFragmentManager.commit(true) {
setReorderingAllowed(true)
val f = create()
replace(R.id.mainFragmentContainer, f, f.javaClass.name)
}
}