kotlinKotlin

Kotlin 入门(三):高阶函数

2019-02-18  本文已影响55人  孤独的根号十二

Lambda 表达式

1.lambda 表达式总是被大括号括着
2.其参数(如果有的话)在->之前声明(参数类型可以省略)

  1. 函数体(如果存在的话)在 ->后面。
    4.在 Kotlin 中有一个约定,如果函数的最后一个参数是一个函数,并且你传递一个 lambda 表达 式作为相应的参数,你可以在圆括号之外指定它

一个 lambda 表达式或匿名函数是一个“函数字面值”,即一个未声明的函数, 但立即做为表达 式传递。考虑下面的例子:

max(strings,    {   a,  b   ->  a.length    <   b.length    })

函数 max 是一个高阶函数,换句话说它接受一个函数作为第二个参数。 其第二个参数是一 个表达式,它本身是一个函数,即函数字面值。写成函数的话,它相当于:

fun compare(a:  String, b:  String):    Boolean =   a.length    <   b.length

对于接受另一个函数作为参数的函数,我们必须为该参数指定函数类型。 例如上述函数 max 定义如下:

fun <T> max(collection:Collection<T>,   less:   (T, T)->    Boolean):   T?  {
var max:    T?  =   null
for (it in  collection)
if(max  ==  null    ||  less(max,   it))    
max =   it
return  max 
}
}

参数 less 的类型是 (T, T) -> Boolean ,即一个接受两个类型 T 的参数并返回一个布尔值 的函数: 如果第一个参数小于第二个那么该函数返回 true。

匿名函数

上面提供的 lambda 表达式语法缺少的一个东西是指定函数的返回类型的 能力。在大多数情 况下,这是不必要的。因为返回类型可以自动推断出来。然而,如果 确实需要显式指定,可 以使用另一种语法: 匿名函数 。

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

匿名函数看起来非常像一个常规函数声明,除了其名称省略了。其函数体 可以是表达式(如 上所示)或代码块。
匿名函数的返回类型推断机制与正常函数一样:对于具有表达式函数体的匿名函数将自动 推 断返回类型,而具有代码块函数体的返回类型必须显式 指定(或者已假定为Unit )。

请注意,匿名函数参数总是在括号内传递。 允许将函数 留在圆括号外的简写语法仅适用于 lambda 表达式。

Lambda表达式和匿名函数之间的另一个区别是 非局部返回的行为。一个不带标签的 return 语句 总是在用 fun 关键字声明的函数中返回。这意味着 lambda 表达式中的 return 将从 包含它的函数返回,而匿名函数中的 return 将从匿名函数自身返回。

闭包

Lambda 表达式或者匿名函数(以及局部函数和对象表达式) 可以访问其 闭包 ,即在外部作 用域中声明的变量。 与 Java 不同的是可以修改闭包中捕获的变量:

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

高阶函数

高阶函数是将函数用作参数或返回值的函数。这种函数的一个很好的例子是 lock() ,它接 受一个锁对象和一个函数,获取锁,运行函数并释放锁:

fun <T> lock(lock:  Lock,   body:   ()  ->  T): T{
lock.lock()
try {
return  body()
}finally{
lock.unlock()
} }

让我们来检查上面的代码: body 拥有函数类型: () -> T , 所以它应该是一个不带参数并 且返回 T 类型值的函数。 它在 try -代码块内部调用、被 lock 保护,其结果 由 lock() 函数返回。
如果我们想调用 lock() 函数,我们可以把另一个函数传给它作为参数,通常会更方便的另一种方式是传一个 lambda 表达式:

fun toBeSynchronized()  =   sharedResource.operation()
val result  =   lock(lock,  ::toBeSynchronized)

val result  =   lock(lock,  {   sharedResource.operation()  })

内联函数

使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,并且会捕获一个闭 包。 即那些在函数体内会访问到的变量。 内存分配(对于函数对象和类)和虚拟调用会引入 运行时间开销。
但是在许多情况下通过内联化 lambda 表达式可以消除这类的开销。
下述函数是这种情况的 很好的例子。即lock()函数可以很容易地在调用处内联。考虑下面的情况:

lock(l) {   foo()   }

编译器没有为参数创建一个函数对象并生成一个调用。取而代之,编译器可以生成以下代 码:

l.lock() try    {
foo() 
} finally{
l.unlock()
 }

这个不是我们从一开始就想要的吗?
为了让编译器这么做,我们需要使用 inline 修饰符标记 lock() 函数:

inline  fun lock<T>(lock:   Lock,   body:   ()  ->  T): T
{               //  …… }

inline 修饰符影响函数本身和传给它的 lambda 表达式:所有这些都将内联 到调用处。
内联可能导致生成的代码增加,但是如果我们使用得当(不内联大函数),它将在 性能上有 所提升,尤其是在循环中的“超多态(megamorphic)”调用处。

Lambda表达式的返回

Kotlin 有函数字面量、局部函数和对象表达式。因此 Kotlin 的函数可以被嵌套。 标签限制的 return 允许我们从外层函数返回。 最重要的一个用途就是从 lambda 表达式中返回。

fun foo()   {       
    ints.forEach    {   
    if  (it ==  0)
    return      
    print(it)               
} }

//从 lambda  表达式中返回,
fun foo()   {           
ints.forEach    lit@    {       
    if  (it ==  0)  
return@lit                  
print(it)               } }

//下划线用于未使用的参数
map.forEach {   _,  value   ->  println("$value!")  } 

常用高阶函数

1.forEach
提供了遍历集合对象的功能,这里只查看IntArray类的forEach方法实现,源码在_Arrays.kt文件中。

public inline fun IntArray.forEach(action: (Int) -> Unit): Unit {
    for (element in this) action(element)
}

// 使用
val arr = intArrayOf(1, 2, 4, 6)
arr.forEach {
    println(it)
}

2.let
它接收了调用者作为参数并且返回任意的类型的lambda表达式,最后以自己为参数调用lambda表达式
代码:

public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

使用:

val arr = intArrayOf(1, 2, 4, 6)
arr.let {
    var sum = 0
    // 这个it就是arr对象
    for (i in it) {
        sum += i
    }
    println(sum)
}

3.map
map就是常用映射,函数其实就是一种映射关系,将输入的参数映射成输出的参数值。

public inline fun <R> IntArray.map(transform: (Int) -> R): List<R> {
    return mapTo(ArrayList<R>(size), transform)
}

public inline fun <R, C : MutableCollection<in R>> IntArray.mapTo(destination: C, transform: (Int) -> R): C {
    for (item in this)
        destination.add(transform(item))
    return destination
}

//使用
val arr = intArrayOf(1, 2, 4, 6)
val newArr = arr.map { (it * 2).toString()  }
println(newArr)

4.flatMap
flatMap是一种支持二维集合映射的高阶函数。

public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> {
    return flatMapTo(ArrayList<R>(), transform)
}

public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C {
    for (element in this) {
        val list = transform(element)
        destination.addAll(list)
    }
    return destination
}

使用:

val arr = intArrayOf(1, 2, 4, 6)
val arr2 = intArrayOf(10, 39, 39, 18, 88)
var arr3 = intArrayOf(100, 200, 383, 198)

val newArr = arrayListOf(arr, arr2, arr3)
val flatArr = newArr.flatMap {
    iterator -> iterator.map {
        it.toString()
    }
}

5.filter
对集合里的元素做过滤操作,只有那些符合要求的对象才需要用户做处理

public inline fun IntArray.filter(predicate: (Int) -> Boolean): List<Int> {
    return filterTo(ArrayList<Int>(), predicate)
}

public inline fun <C : MutableCollection<in Int>> IntArray.filterTo(destination: C, predicate: (Int) -> Boolean): C {
    for (element in this) if (predicate(element)) destination.add(element)
    return destination
}

使用:

val arr = intArrayOf(1, 2, 4, 6, 10, 39, 39, 18, 88)
val newArr = arr.filter { it % 2 == 0 }
println(newArr)

// 输出结果
// [2, 4, 6, 10, 18, 88]

6.takeWhile
takeWhile和filter一样都是过滤用的函数,它的实现和filter不同地方在filter总是会遍历当前IntArray的所有元素,而takeWhile在第一次发现predict不满足的时候就不再遍历,后面的元素即使满足条件也不会加入到结果中。

public inline fun IntArray.takeWhile(predicate: (Int) -> Boolean): List<Int> {
    val list = ArrayList<Int>()
    for (item in this) {
        if (!predicate(item))
            break
        list.add(item)
    }
    return list
}

使用:

val arr = intArrayOf(1, 2, 4, 6, 10, 39, 39, 18, 88)
val newArr = arr.takeWhile { it % 2 == 0 }
println(newArr)

// 输出结果为空,因为第一个1不是偶数,直接返回,没有任何结果
// []

7.take/takeLast
take是从集合中取前几个元素,takeLast是从集合中取后几个元素。

public fun IntArray.take(n: Int): List<Int> {
    require(n >= 0) { "Requested element count $n is less than zero." }
    if (n == 0) return emptyList()
    if (n >= size) return toList()
    if (n == 1) return listOf(this[0])
    var count = 0
    val list = ArrayList<Int>(n)
    for (item in this) {
        if (count++ == n)
            break
        list.add(item)
    }
    return list
}

public fun IntArray.takeLast(n: Int): List<Int> {
    require(n >= 0) { "Requested element count $n is less than zero." }
    if (n == 0) return emptyList()
    val size = size
    if (n >= size) return toList()
    if (n == 1) return listOf(this[size - 1])
    val list = ArrayList<Int>(n)
    for (index in size - n .. size - 1)
        list.add(this[index])
    return list
}

使用:

val arr = intArrayOf(1, 2, 4, 6, 10, 39, 39, 18, 88)

// [1, 2]
println(arr.take(2))

// [18, 88]
println(arr.takeLast(2))

8.fold
把集合里的所有元素结合成一个值。fold顾名思义就是折叠起来,不过它会提供一个初始值。

public inline fun <R> IntArray.fold(initial: R, operation: (acc: R, Int) -> R): R {
    var accumulator = initial
    for (element in this)
        accumulator = operation(accumulator, element)

    return accumulator
}

使用:

val arr = intArrayOf(1, 2, 4, 6, 10, 39, 39, 18, 88)
arr.fold(2) { product, element ->
    product * element
}

9.reduce
reduce也就是规约的意思,也是把多个值融合成一个值的操作,不过它并不会提供一个初始值。

public inline fun IntArray.reduce(operation: (acc: Int, Int) -> Int): Int {
    if (isEmpty())
        throw UnsupportedOperationException("Empty array can't be reduced.")
    var accumulator = this[0]
    for (index in 1..lastIndex) {
        accumulator = operation(accumulator, this[index])
    }
    return accumulator
}

使用:

val arr = intArrayOf(1, 2, 4, 6, 10, 39, 39, 18, 88)
arr.reduce { product, element ->
    product * element
}

10.apply
apply用于在lambda表达式里切换上下文的高阶函数

public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

使用:

class DbConfig {
    var url: String = ""
    var username: String = ""
    var password: String = ""

    override fun toString(): String {
        return "url = $url, username = $username, password = $password"
    }
}

class DbConnection {
    fun config(conf: DbConfig) {
        println(conf)
    }
}

fun main(args: Array<String>) {
    val conn = DbConnection()
    conn.config(DbConfig().apply {
        url = "mysql://127.0.0.1:3306/hello"
        username = "root"
        password = "123456"
    })
}

11.with
ith可以让用户省略点号之前的对象引用,with内部的所有操作都是针对with对象

public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

使用:

class MyLogger {
    var tag: String = "TAG"

    fun e(msg: String) {
        println("$tag  $i")
    }

    fun tag(tagStr: String) {
        tag = tagStr
    }
}

fun main(args: Array<String>) {
    val logger = MyLogger()
    with(logger) {
        tag("Kotlin")
        e("It is a good language")
    }
}

12.use
针对那些实现了Closable接口的对象的扩展方法,也就是大部分的IO操作相关类会有这个扩展高阶方法,查看它的源代码在Closable.kt源文件中。

public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        when {
            apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
            this == null -> {}
            exception == null -> close()
            else ->
                try {
                    close()
                } catch (closeException: Throwable) {
                    // cause.addSuppressed(closeException) // ignored here
                }
        }
    }
}

使用:

val file = File("test.txt")
val bufferReader = BufferedReader(FileReader(file))
bufferReader.use {
    it.readLine()
}
上一篇 下一篇

猜你喜欢

热点阅读