一、函数类型
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
- 顶层、局部、成员、扩展函数;示例:
-
使用实现函数类型接口的自定义类的实例
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、官方自带高阶函数分析
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)
}
}