Kotlin 学习笔记08

287 阅读4分钟

Lambda作为形参和返回值

声明高阶函数

  • 任何以lambda或者函数引用作为参数的函数,或者返回值,或者两者都有,就是高阶函数。比如list.filter
    (4,"abc")-> {} 如下:

{ x, y -> x + y} 这里省略了参数x,y类型,因为在其他部分已经制定了,不需要在lambda函数体再次声明

val funOrNull: ((Int, Int) -> Int)? = null
  • 可以为函数参数制定名字
//声明
fun performRequest(
        url: String,
        callback: (code: Int, content: String) -> Unit
){}
//调用
val url = "http://baidu.com"
performRequest(url,{code, content -> /*code*/ })
performRequest(url){code, content -> /*code*/ }//根据kotlin约定,lambda为最后一个参数,可以放到括号外面
  • 调用函数
//声明一个函数,参数为一个函数,这个函数有2个参数都是int值,返回值也为int
fun towAndThree(operator: (x: Int, y: Int) -> Int) {//这里的x,y是可以省略的,省略了,IDE代码补全会不方便
    val result = operator(3, 4)
    print("the result is $result")
}
  • filter()函数
//声明
public inline fun <C : Appendable> CharSequence.filterTo(destination: C, predicate: (Char) -> Boolean): C {
    for (index in 0 until length) {
        val element = get(index)
        if (predicate(element)) destination.append(element)
    }
return destination
}
//调用
println(url.filter { it in 'a'..'z' })

  • 在Java中使用函数类 原理:函数类型被声明为普通的接口:一个函数类型的变量,是FunctionN接口的一个实现。Kotlin标准库定义了一系列的接口比如 Function0<R>(没有参数的函数),Function1(P1,R)(一个参数的函数),调用这个方法就会执行函数。一个函数类型的变量就实现了对应的FunctionN接口的实现类的实例,实现类invoke方法包含了lambda函数体
//kotlin声明
fun processTheAnser(f: (Int) -> Int){}
//java 调用 java 8
processTheAnder(num->num+1)
//在Java8以下的版本这样调用
procressTheAnder{
    new Function1<Integer,Integer>(){
        @Override
        public Integer invoke(Integer number){
            return number + 1
        }
    }
}

函数类型的参数默认值和null

fun <T> Collection<T>.joinToString(
        separator: String = ", ",
        prefix: String = "",
        postfix: String = ""
): String {
    val result = StringBuilder(prefix)
    for ((index, value) in this.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(value.toString())
    }
    result.append(postfix)
    return result.toString()
}

返回函数的函数

  • 比如运输费用依赖于运输方式,可以定义一个函数,用来选择恰当的逻辑变体,并将它作为一个函数返回
fun getShoppingCarCacul(delivery: Delivery): (Order) -> Double {
    if (delivery == Delivery.STANDARD) {
        return { order: Order -> 6 + 2.1 * order.itemCont }
    }
    return { order -> 1.2 * order.itemCont }
}
//调用
var shoppingCarCacul = getShoppingCarCacul(Delivery.EXPEDITED)
println("产生的运费:${shoppingCarCacul(Order(4))}")
  • eg:通过输入开头文字,过滤联系人列表
data class Man(
        val firstName: String,
        val lastName: String,
        val phoneNumber: String?
)

class ContactListFilter() {
    val prefix = ""
    val isOnlyPhoneNumber = false

    fun qetPredicate(): (Man) -> Boolean {
        val startWithPrefix = { man: Man -> man.firstName.startsWith(prefix) || man.lastName.startsWith(prefix) }
        //这里不能使用it,因为IDE推断不出it代表是什么
        if (!isOnlyPhoneNumber) {
            return startWithPrefix
        }
        return { man: Man -> startWithPrefix(man) && man.phoneNumber != null }
        //这里可以是用it,这里是根据方法的返回值类型,推断出it代码得man
    }
}
//调用
 val mans = listOf(Man("Bob", "jack"), Man("BooBo", "jack", "13800138000"), Man("kk", "jack", "13800138000"))
    val contactListFilter = ContactListFilter()
    with(contactListFilter) {
        prefix = "B"
        isOnlyPhoneNumber = false
    }
println(mans.filter(contactListFilter.qetPredicate()))

使用lambda去除重复代码

 val log = listOf(
            SiteVisit("/", 34.0, OS.WINDOWS),
            SiteVisit("/", 22.0, OS.MAC),
            SiteVisit("/", 12.0, OS.WINDOWS),
            SiteVisit("/sign_up", 8.0, OS.IOS),
            SiteVisit("/", 16.3, OS.ANDROID)
    )
fun List<SiteVisit>.averageTime(os: OS) = filter { it.os == os }
        .map { it.duration }
        .average()

//调用
println(log.averageTime(OS.WINDOWS))
  • 如果说要查询WINDOW、和Android的呢?后面又需要改成来自iOS的注册页面停留时间是多少呢?这个时候lambda就派上用场了。
fun List<SiteVisit>.averageTime(predicate: (SiteVisit) -> Boolean) = filter(predicate)//这里的筛选改为lambda。
        .map { it.duration }
        .average()

把过滤条件转换成lambda表达式,实现去除重复代码
策略模式(你需要声明一个接口,并且为每一种可能实现不同的策略)可以通过lambda简化

内联函数:消除lambda运行时带来的开销

在kotlin中,每创建一个lambda表达式就会创建一个匿名类,so,每次调用都会创建一个对象,会带来额外的开销这个时候inline就出现了。
一个被inline修饰的函数,函数体会直接替换到函数被调用的地方,而不是正常调用。一般来说,参数如果被直接调用或者作为参数传递给另外一个inline函数。他是可以被内联的。

  • 内联函数使用限制-》不能把内联函数保存到一个属性?

内联集合操作

Kotlin标准库中,集合函数,比如filtermap等函数已经inline函数,不会产生额外的对象。在处理比较大的集合,应该使用序列asSequence进行操作。

怎样决定是否使用lambda

  • 使用inline关键字只能提高带有lambda参数的函数的性能。
  • 对于普通函数JVM已经提供了强大的内联支持。
  • 应该保证inline修饰的函数比较小。-》因为需要把函数字节码拷贝到每一个调用点上。

使用lambda管理资源

通常在资源管理中,需要在try里获取资源,在finally中释放资源可以把这一部分封装成lambda表达式 在Kotlin中,加锁使用withLock函数 :try-with-resource 语句

static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }//这里不需要写关闭资源的语句,
}

在Kotlin中使用use实现相同的结果,用于操作可关闭的资源。

fun readFirstLineFromFile(path: String): String = BufferedReader(FileReader(path)).use { br -> br.readLine() }

高阶函数中的控制流

  • 使用return 从一个封闭的函数中返回。
val list = listOf(Person("Bob", 18, 20000), Person("Jack", 19, 20000))
    list.forEach {
        if (it.name=="Bob") {
            println("Found!")
            return //直接返回方法
        }
    }
  • 从lambda返回
val list = listOf(Person("Bob", 18, 20000), Person("Jack", 19, 20000))
    list.forEach {
        if (it.name=="Bob") {
            println("Found!")
            return@forEach //直接返回方法
        }
    }

同样的规则也适用于this表达式

  • 匿名函数:默认使用局局部返回
list.forEach {
        if (it.name=="Bob") return
        println("Found!")
    }

filter中使用匿名函数

    list.filter(
            fun(person): Boolean {
                return person.age > 10
            }
    )

如果是使用表达式函数体,可以省略返回值类型,可以简化为

list.filter(
        fun(person) = person.age > 10
)

End