高阶函数-Kotlin

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

  • 使用实现函数类型接口的自定义类的实例

Kotlin 复制代码
//函数类型可以当成接口实现
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类型值的地方,反之亦然

Kotlin 复制代码
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
}

默认情况下推断出的是没有接收者的函数类型,即使变量是通过扩展函数引用来初始化的。如果需要接收者方式调用,需要显式指定函数类型

Kotlin 复制代码
//注意这个要写在顶层函数里,局部函数无法通过 类型::方法名的方式引用
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、高阶函数

高阶函数是将函数用作参数或者返回值的函数

Kotlin 复制代码
//示例:
fun calculate(x: Int, cal: (Int) -> Int): Int {
        return cal(x)
}

上述示例,除了传入正常的参数x ,还有一个参数cal 它的类型就是函数类型(Int) -> Int。因此calculate函数就是一个高阶函数,可以接收一个函数类型的实例作为参数

Kotlin 复制代码
//调用示例
fun main() {
	  //传入一个lambda表达式作为函数实例
		////如果函数参数在最后一个,lambda表达式作为参数传入时可以放在圆括号之外
    calculate(100) { x -> x + x } 
}

二、高阶函数运用

2.1、官方自带高阶函数分析

高阶函数 let、apply、also、with 分析

2.2、自定义高阶函数---多参数判空

我们经常会遇到一个场景,需要同时对多个参数进行判空,任一参数为空则不继续执行

在Java中需要一个个参数判断,如果参数很多代码很长而且复杂,可以写一个多参数判空的Kotlin的高阶函数来简化该逻辑

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实例,将这个场景抽象的同一个方法中

但是通过高阶函数我们可以直接通过一个方法实现,也无法新增接口;实例如下:

Kotlin 复制代码
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)
        }
    }
相关推荐
深海呐3 小时前
Android AlertDialog圆角背景不生效的问题
android
ljl_jiaLiang3 小时前
android10 系统定制:增加应用使用数据埋点,应用使用时长统计
android·系统定制
花花鱼3 小时前
android 删除系统原有的debug.keystore,系统运行的时候,重新生成新的debug.keystore,来完成App的运行。
android
落落落sss4 小时前
sharding-jdbc分库分表
android·java·开发语言·数据库·servlet·oracle
一丝晨光5 小时前
逻辑运算符
java·c++·python·kotlin·c#·c·逻辑运算符
消失的旧时光-19436 小时前
kotlin的密封类
android·开发语言·kotlin
服装学院的IT男8 小时前
【Android 13源码分析】WindowContainer窗口层级-4-Layer树
android
CCTV果冻爽9 小时前
Android 源码集成可卸载 APP
android
码农明明9 小时前
Android源码分析:从源头分析View事件的传递
android·操作系统·源码阅读
秋月霜风10 小时前
mariadb主从配置步骤
android·adb·mariadb