Kotlin笔记8-Higher-Order Functions and Lambdas 高阶函数和Lambda表达式

目录

1. Higher-Order Functions 高阶函数

2. function types 函数式类型

3. 实例化函数式类型 Instantiating a function type

4. 调用一个函数类型变量 Invoking a function type instance

5. Lambdas表达式和匿名函数Anonymous Functions

(1)Lambdas表达式

(2)匿名函数Anonymous Functions

6. 闭包 Closures


在kotlin中,函数是一等公民,这意味着它可以存储在变量和其他数据结构当中,当作参数传递给别的函数,或者作为函数的返回值

为了让函数式编程更容易,Kotlin提供了函数类型function types 来代表函数,并且提供了一组特定的语言结构,比如Lambdas表达式。

1. Higher-Order Functions 高阶函数

即函数的参数是一个函数类型,或者返回值是函数类型

2. function types 函数式类型

Kotlin使用一系列的函数式类型,如(A, B) -> C 来声明一个代表函数的类型,输入参数是A和B,输出类型是C,输入参数为空的时候,可以使用() -> A表示,返回值类型不能省略。

函数式类型可以有一个额外的接收者receiver 类型,在逗号之前声明,

如A.(B) -> C,表示这个函数可以被A类型的接收者对象调用。

一些函数式类型的说明:

(1)参数列表可以添加一个命名,如(x: Int, y: Int) -> Point,这样可以更清晰地说明参数代表的意思。

(2)表示一个返回值是可空的函数式类型  :  ((Int, Int) -> Int)?.

(3)函数式类型可以嵌套,如 : (Int) -> ((Int) -> Unit)  ,由于 ->是右关联性的,所以这等价于(Int) -> (Int) -> Unit

(4)可以通过使用typealias给函数式类型起一个别名 如: typealias ClickHandler = (Button, ClickEvent) -> Unit

3. 实例化函数式类型 Instantiating a function type

有多种方式可以来实例化一个函数式类型:

(1)使用function literals函数字面值,

       ----Lambda表达式 :  { a, b -> a + b }

       ----匿名函数  :  fun(s: String): Int { return s.toIntOrNull() ?: 0 }

(2)通过函数引用符号 “:: ”来引用一个已经存在的函数声明

   ----顶级函数、局部函数、成员函数、或者扩展函数 如 ::isOddString::toInt

       ----顶级属性、成员属性、或者扩展属性,如: List<Int>::size

       ----构造函数 如: ::Regex

(3)通过一个自定义的,实现了函数类型接口的类的实例,如:

class IntTransformer: (Int) -> Int {
      override operator fun invoke(x: Int): Int = TODO()
  }
    
  val intFunction: (Int) -> Int = IntTransformer()

如果有足够的信息,编译器就能推断函数式变量的类型,而有接收者receiver时,则不能省略函数类型,如:

val a = { i: Int -> i + 1 } // 编译器可以推断出变量类型是 (Int) -> Int,所以可以省略

//有接收者receiver时,变量类型String.(Int) -> String不能省略
val repeatFun: String.(Int) -> String = { times -> this.repeat(times) }

⚠️注意:一个函数类型变量,不管有还是没有接收者receiver,它们都是可以互换的,所以接收者receiver可以代表lambda表达式中的第一个参数,反之亦然。比如:一个(A, B) -> C类型的变量,可以传递或者赋值给一个需要A.(B) -> C类型的变量,反之亦然。

fun main(args: Array<String>) {
    val repeatFun: String.(Int) -> String = { times -> this.repeat(times) }
    val twoParameters: (String, Int) -> String = repeatFun // OK

    fun runTransformation(f: (String, Int) -> String): String {
        return f("hello", 3)
    }
    val result = runTransformation(repeatFun) // OK

    println("result = $result")
}
//输出 result = hellohellohello

 

4. 调用一个函数类型变量 Invoking a function type instance

一个函数类型的变量可以通过使用invoke操作符来调用 : f.invoke(x) 或者 f(x)

如果这个函数类型的变量有一个接收者receiver,可以将接收者作为第一个参数传递进参数列表,或者在变量前添加接收者receiver的前缀,就像是该变量如同一个扩展函数一样,如:

    val stringPlus: (String, String) -> String = String::plus
    val intPlus: Int.(Int) -> Int = Int::plus

    println(stringPlus.invoke("<-", "->"))
    println(stringPlus("Hello, ", "world!")) 

    println(intPlus.invoke(1, 1))
    println(intPlus(1, 2))
    println(2.intPlus(3)) // 类似于扩展函数式的调用函数类型变量

5. Lambdas表达式和匿名函数Anonymous Functions

(1)Lambdas表达式

Lambda表达式和匿名函数Anonymous Functions就称做函数字面值function literals,function literals不是一种声明,而是作为一个表达式立即传递出去,

完整的lambda表达式语法如下:

val sum = { x: Int, y: Int -> x + y }

lambda表达式总是被大括号括起来,参数(如果有的话)在->前声明(参数类型可以省略),函数体在->后面。

如果编译器推断出来的lambda表达式返回值类型不是Unit,则最后一个表达式的值就是整个lambda表达式的返回值

省略可选的声明后,lambda表达式就像下面这样:

val sum: (Int, Int) -> Int = { x, y -> x + y }
//(Int, Int) -> Int表示函数式类型 
//{ x, y -> x + y } 就是简略后的lambda表达式

如果lambda表达式是最后一个参数,则可以放到圆括号外面,如果lambda表达式是唯一的参数,可以省略圆括号,如:

//lambda表达式是最后一个参数时,可以放到圆括号外面
val product = items.fold(1) { acc, e -> acc * e }

list.filter ({
   it % 2 == 1
})
//lambda表达式是唯一的一个参数时,可以省略圆括号
list.filter {
   it % 2 == 1
}

lambda表达式如果只有一个参数,那么可以省略参数声明,同时省略->, 用it来指代参数,如:

ints.filter { it > 0 } // this literal is of type '(it: Int) -> Boolean'

lambda表达式的返回值,除了隐式的返回最后一个表达式的值,还可以通过 qualified return语法显式的指明,如以下代码段是等价的:

ints.filter {
    val shouldFilter = it > 0 
    shouldFilter
}

ints.filter {
    val shouldFilter = it > 0 
    return@filter shouldFilter
}

这种传递一个lambda表达式,省略圆括号的转换,使得链式调用语法 LINQ-style得以运用:

strings.filter { it.length == 5 }.sortedBy { it }.map { it.toUpperCase() }

从Kotlin 1.1 开始,如果一个lambda参数没有在函数体里使用到,可以用下划线代替参数名,如:

map.forEach { _, value -> println("$value!") }

在某些情况下,可以将对象表达式改写成Lambda表达式:

import java.util.*

fun getList(): List<Int> {
    val arrayList = arrayListOf(1, 5, 2)
    Collections.sort(arrayList, object : Comparator<Int> {
        override fun compare(x: Int, y: Int): Int {
            return y - x
        }
    })
    return arrayList
}

//改成lambda表达式后
   Collections.sort(arrayList) { x, y ->
        y - x
    }

//Java中Collections类的静态方法调用应该替换成Kotlin中的标准库函数,这里改成list.sortWith()
  arrayList.sortWith(comparator = Comparator { x, y ->
        y - x
    })

//Comparator是一个接口,可以使用lambda表达式来实现该接口中的方法

 

(2)匿名函数Anonymous Functions

lambda表达式的类型可以被编译器自动推断出来,就是函数类型,如果你非要明确指定它是一个函数式类型,可以使用替代的语法:匿名函数

fun(x: Int, y: Int): Int = x + y

匿名函数的函数名是省略的,函数体可以是一个表达式或者是一个代码块,参数类型如果能从上下文中推断出来,可以省略,如:

ints.filter(fun(item) = item > 0)

匿名函数的参数必须全部放在圆括号里,这跟lambda表达式可以将参数放在圆括号外面不同。

另外一个匿名函数和lambda表达式不同的地方是non-local returns的行为,lambda表达式中的return语句从整个函数体返回,而匿名函数中的return只从其自身返回。

6. 闭包 Closures

lambda表达式、匿名函数、local function 局部函数 和object expression对象表达式 都可以访问其闭包在外部作用域中声明的变量,并可以改变其值,这一点和Java不同,如:

var sum = 0
ints.filter { it > 0 }.forEach {
    sum += it
}
print(sum)

猜你喜欢

转载自blog.csdn.net/unicorn97/article/details/81436231