文章

Kotlin高级——高阶函数

开始

高阶函数是将函数作为参数和返回值的函数,函数可以作为对象进行传递,函数对象的类型由参数和返回值表示,参数名可省略

1
2
3
4
(Int, Int) -> Int
() -> Unit // 最简单的函数
// 一个函数对象也可以如下声明
val method: (Int) -> Int

函数类型支持可空类型,支持嵌套,支持类型别名

1
2
3
4
((Int, Int) -> Unit)? // 可空类型
(Int) -> ((Int) -> Unit) // 嵌套表示
(Int) -> (Int) -> Unit // ->符号是右结合的
typealias Handler = () -> Unit // 类型别名

函数类型实例化

通过以下方式可以实例化一个函数类型,lambda表达式和匿名函数是函数字面量,通过invoke方法或使用调用操作符method()调用函数对象

  • lambda表达式
  • 匿名函数
  • 使用具名函数引用
    • 顶层函数、局部函数、扩展函数:::method
    • 成员方法:String::toInt
    • 主构造器:::ClassName

匿名函数

可以指定返回类型,返回类型推断与具名函数相同

1
2
3
4
5
6
7
val f: (Int, Int) -> Int = fun (a: Int, b: Int): Int {
    return a + b
}
// 支持单表达式
val h: (Int, Int) -> Int = fun (a: Int, b: Int): Int = a + b
// 支持类型推断,类型声明和返回值类型不可省略
val m = fun (a: Int, b: Int): Int = a + b

lambda表达式

1
2
3
4
5
6
7
8
9
10
val f: (Int, Int) -> Int = {
    // 参数写在括号内部,类型声明可以忽略
    a: Int, b: Int -> // 最后一个表达式作为返回值
    // statement
    // expression
}
// 支持类型推断,类型声明和返回值类型
val h = {
    a: Int, b: Int -> a + b
}

简写语法

  • 当lambda表达式是函数的最后一个方法时,lambda表达式传参可以放在括号外面
  • 当函数只有lambda表达式一个参数时,函数的括号可以省略
  • 当lambda表达式只有一个参数时,可以省略参数声明,使用隐式参数it
  • 若lambda表达式中的参数未使用,在参数声明处使用_替代

SAM

函数式接口SAM:当接口中只有一个抽象方法时,接口称为函数式接口,函数式接口可以进行SAM转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface Handler {
    fun handle()
}
val handler: Handler = object : Handler {
    override fun handle() {
        // 匿名对象写法
    }
}
// 函数式接口可以添加fun关键字,开启lambda表达式支持
// 不是函数式接口添加fun关键字会编译错误
fun interface Comsumer {
    fun accept()
}
val consumer: Consumer = Consumer {
    // Lambda表达式
}

函数字面量的接收者

一个函数类型中可以带有接收者,表示指定接收者中的一个函数类型

1
2
3
4
5
class Person {
    // 该函数的接收者函数类型为Person.() -> Unit
    fun say() {
    }
}

通过lambda和匿名函数两种字面量获取接收者函数

1
2
3
4
// 在字面量内部,隐式传入了接收者引用this
// 带有接收者时,lambda不支持类型推断,匿名函数支持类型推断,类型声明和返回值类型不可省略
val f: Person.() -> Unit = { say() }
val m: Person.() -> Unit = fun Person.(): Unit { say() }

限定this表达式

this默认指向最内层的作用域对象(类、扩展函数、带有接收者的函数字面量),可以使用限定this表达式获取外部作用域的this,格式为this@label,由于类声明无法设置标签,因此通常使用隐式标签

当调用this的属性和方法时,可以省略this,但存在同名顶层函数或顶层属性时,最好指定this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class A {
    inner class B {
        // Int的扩展函数,接收者为Int
        fun Int.foo() { // 隐式标签@foo
            val a = this@A // A的this
            val b = this@B // B的this

            val c = this // foo()的接收者Int
            val c1 = this@foo // foo()的接收者Int

            // 带有String接收者的匿名函数字面量
            val funLit = fun String.() {
                val d = this // funLit的接收者String
            }
            
            // 带有String接收者的lambda函数字面量
            val funLit1: String.() -> Unit = {
                val d1 = this // funLit1的接收者String
            }

            val funLit2 = { s: String ->
                // foo()的接收者Int,因为它包含的lambda表达式没有任何接收者
                val d2 = this
            }
        }
    }
}

内联函数

当一个函数接收一个lambda表达式作为参数,lambda表达式在调用时会产生一个函数对象,且会捕获闭包,当lambda表达式过多时性能开销较大,此时可以使用内联提升性能

在需要传入lambda参数的函数上用inline关键字修饰,此时该函数称为内联函数

直接return

java的lambda表达式中支持return,本质是一个方法的返回

kotlin的lambda表达式中不支持直接使用return,需要使用标签才能返回

使用内联后,内联函数会将lambda表达式中的代码直接插入到调用处,减少了lambda表达式对象的创建,当lambda表达式中包含return时,return也会被内联到内联函数中,因此return是从内联函数中返回

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
    f {
        println("lambda执行中")
        return
    }
}

inline fun f(h: () -> Unit) {
    println("lambda调用前")
    h()
    // lambda中的return被内联到调用处,直接return,该句不执行
    println("lambda调用后")
}

noinline关键字

使用noinline关键字指定某个lambda表达式不进行内联,通常当传入的函数字面量作为函数对象使用时,不进行内联

1
2
3
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
    // notInlined不进行内联
}

操作符重载

使用operator关键字实现指定名称的成员函数或扩展函数,Kotlin只支持有限的几种操作符重载

1
2
3
4
5
6
class OrdersList: IndexedContainer {
    // 重载访问操作符
    operator fun get(index: Int) { 
        // statements
    }   
}

一元操作

表达式翻译为
+aa.unaryPlus()
-aa.unaryMinus()
!aa.not()
a++a.inc()
a--a.dec()

二元操作

表达式翻译为
a + ba.plus(b)
a - ba.minus(b)
a * ba.times(b)
a / ba.div(b)
a % ba.rem(b)
a..ba.rangeTo(b)
a..<ba.rangeUntil(b)
a in bb.contains(a)
a !in b!b.contains(a)
a == ba?.equals(b) ?: (b === null)
a != b!(a?.equals(b) ?: (b === null))
a > ba.compareTo(b) > 0
a < ba.compareTo(b) < 0
a >= ba.compareTo(b) >= 0
a <= ba.compareTo(b) <= 0
a += ba.plusAssign(b)
a -= ba.minusAssign(b)
a *= ba.timesAssign(b)
a /= ba.divAssign(b)
a %= ba.remAssign(b)

其他操作符

索引访问操作符翻译为
a[i]a.get(i)
a[i, j]a.get(i, j)
a[i_1, ……, i_n]a.get(i_1, ……, i_n)
a[i] = ba.set(i, b)
a[i, j] = ba.set(i, j, b)
a[i_1, ……, i_n] = ba.set(i_1, ……, i_n, b)
invoke操作符翻译为
a()a.invoke()
a(i)a.invoke(i)
a(i, j)a.invoke(i, j)
a(i_1, ……, i_n)a.invoke(i_1, ……, i_n)
本文由作者按照 CC BY 4.0 进行授权