Kotlin中的函数类型

函数类型和高阶函数

Kotlin中的函数就像类一样,拥有函数类型、函数实例,并且可以作为方法的参数和返回值。将函数类型作为参数的函数或者返回值是函数类型的函数就称为高阶函数 。

(View) -> Unit
val onClickListener : (View) -> Unit
fun performClick( ((View) -> Unit))? )

上述代码首先定义了一个函数类型 (View) -> Unit,函数类型描述了函数的参数列表以及返回值,中间用箭头连接。

然后声明了一个函数对象 onClickListener,其类型即前述定义的函数类型。

最后定义了一个名为 performClick 的高阶函数,其参数即为前述的函数类型,并且该参数可以为 null, 表示函数的可空类型首先需要在函数类型外层用 () 包括,然后再跟一个 ? 表示可空。

函数类型还可以指定函数的调用者类型,即这个函数只能被特定类型的实例调用,例如:

 View.(View) -> Unit

即表示该函数类型只能被View类型的对象调用。

函数类型的实例化

创建一个函数实例通常有以下三种方式:

  • Lamada表达式或者匿名函数对象

    { a, b -> a + b } 
    

    表示初始化了一个函数对象,其对输入的两个参数a和b进行求和。

    fun (a: Int, b: Int) : Int { return a + b }
    

    表示初始化了一个匿名函数用于实现对a和b进行求和。

  • 通过操作符 :: 来引用已经声明的函数对象

    class  Test {
        fun test() {}
    }
    

    Test 类型中声明了一个名为test的函数,则可以通过 Test::test 这样的语法来引用Test类的test函数。

  • 创建一个实现了函数接口的类的实例

    interface Function0<out R> {
        public operator fun invoke(): R
    }
    

    上述接口表示一个参数为空的函数类型,创建一个实现了该接口的类的实例即产生了一个函数实例。 Kotlin标准库中还提供了大量类似的接口 Function1、Function2、... FunctionN

Lambada 表达式

Lambada 表达式即形似 { a,b -> return a + b } 的函数实例,-> 前面的部分表示函数的输入参数,-> 后面的部分表示函数体。

如果Lambada表达式的返回值不是 Unit 类型,则可以省略显示的return语句,Lambada表达式中的最后一句表达式的值即作为函数的返回值。

如果Lambada表达式只有一个参数,则可以省略掉 -> 和参数名,这个唯一的参数名隐含叫做 it , 在函数体中可以通过 it 来引用这个唯一的参数。

特别地,在指定了函数调用者类型的Lambada表达式中,可以通过 this 关键字来引用调用者的对象本身。例如:

Int.plus(a: Int) = { a -> this + a }

匿名函数对象

Lambada表达式的返回值类型是自动推断的,如果需要显示指定函数的返回值类型,则可以使用匿名函数对象。

fun Int.plus(a: Int): Long  = { 
    return (this + a).toLong() 
}

Inline 函数

有些函数的内容十分简单,则可以通过内联函数来减少函数调用的开销,例如:

list.forEach {
    print(it)
}

fun print(item: Any) {
    System.out.println(item.toString())
}

上述的 print 函数可以通过加上inline关键字来标识成内联函数,这样在编译器编译后函数调用就会被内联:

inline fun print(item: Any) {
    System.out.println(item.toString())
}

此外,Inline函数如果有接收Lambada表达式作为函数的参数,则作为函数参数的Lambada表达式也会被一起内联。

inline fun <T>  T.apply(block: T.()-> Unit): T {
    block()
    return this
}
textView.apply {
    this.text = "test"
}

例如Kotlin标准库中的 apply 函数是一个Inline函数,并且接收一个Lambada表达式作为参数,在该Lambada表达式也会被内联。如果不希望内联函数中Lambada表达式参数也被内联,则可以在参数名前使用noinline关键字声明。

inline fun <T>  T.apply(noline block: T.()-> Unit): T {
    block()
    return this
}

Kotlin中对于函数体中的不带任何 label 的 return 语句固定都是返回到函数声明的fun 关键字处,而Lambada表达式的声明由于没有fun关键字,所以无法使用不带任何 label 的 return 语句直接返回。

fun test() {
    textView.apply {
	this.text = "test"
	return				//compile error ?
    }
}

如上述示例,Lambada中不带任何 label 的 return 语句会报编译错误,但实际上示例中的return语句是不会报错的,原因是apply函数是一个Inline函数,其Lambada表达式也被内联了,内联之后 return 语句实际上就是返回到了外层的test函数处。

Reified 关键字

当我们声明一个带泛型参数的函数时,在函数体内是无法知晓泛型真正的类型的。

fun <T> JSONArray.filter(filterFunc: (T) -> Boolean): List<T> {
    val result = ArrayList<T>()
    if (this.length() > 0) {
        for (i in 0 until this.length()) {
            if (filterFunc.invoke(this[i] as T)) {		//编译器警告Unchecked cast
                result.add(this[i] as T)		//编译器警告Unchecked cast
            }
        }
    }
    return result
}

上述示例中的 filter 函数用于从JSONArray中按照特定的规则过滤出想要的成员,由于JSONArray的成员类型是不确定的,因此声明了filterFunc 的参数类型是泛型,但是上述代码会遇到编译器警告 Unchecked cast 。

消除这个编译器警告,只需要在函数的泛型声明处使用 reified 关键字,但 reified 关键字限定只能在Inline函数中使用,因此还需要将上述函数变成内联函数:

inline fun <reified T> JSONArray.filter(filterFunc: (T) -> Boolean): List<T> {
    this[0] as T		//不会再有编译器警告
}
posted @ 2023-08-16 00:40  jqc  阅读(60)  评论(0编辑  收藏  举报