Lambda表达式和匿名函数

一、函数类型

1.1、函数类型界说

Kotlin运用相似(Int) → String 的函数类型来处理函数的声明

声明规则:

  • 函数类型都有一个圆括号括起来的参数类型列表以及一个回来类型:形如(A, B) -> C 表示接受的类型分别为AB 两个参数并回来C 类型值的函数类型。参数类型列表可认为空,如:() -> CUnit 回来类型 不行省掉

  • 函数类型能够有一个额定的接纳者类型,声明规则是在上述函数声明前面加接纳者类型. 例如

    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 }

    带接纳者类型的函数字面值可用作实例化带有接纳者的函数类型

  • 运用已有声明的可调用引证

    • 顶层、局部、成员、扩展函数;示例:::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)
        }
    }