前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kotlin | 8.高阶函数:Lambda作为形参和返回值

Kotlin | 8.高阶函数:Lambda作为形参和返回值

作者头像
Jingbin
发布2021-03-02 15:56:31
1.1K0
发布2021-03-02 15:56:31
举报
文章被收录于专栏:Android 技术栈

本章内容包括:

  • 函数类型
  • 离阶函数及其在组织代码过程中的应用
  • 内联函数
  • 非局部返回和标签
  • 重名函数

8.1 声明高阶函数

代码语言:javascript
复制
         // 高阶函数就是以另一个函数作为参数或者返回值的函数。
        val list = listOf(0, 1, 2, 3)
        println(list.filter { it > 0 })


        /**-------------------- 8.1.1 函数类型 ----------------------*/
        // Kotlin的类型推导
        val sum = { x: Int, y: Int -> x + y }
        val action = { println(42) }

        // 这些变量的显示类型声明是什么样的?
        // 有两个Int型参数和Int型返回值的函数
        val sum2: (Int, Int) -> Int = { x, y -> x + y }
        // 没有参数和返回值的函数
        val action2: () -> Unit = { println(42) }

        /*
        * (Int, String) -> Unit
        * 参数类型           返回类型
        * 声明函数类型,需要将函数参数类型防在括号中,紧接着是一个箭头和函数的返回类型
        */

        // 标记函数类型 返回值为可空 类型:
        var canReturnNull: (Int, Int) -> Int? = { x, y -> null }

        // 标记 函数类型的可空 变量
        var funOrNull: ((Int, Int) -> Int)? = null

        // 函数类型的参数名:可以为函数类型声明中的参数指定名字:
        fun performRequest(url: String, callback: (code: Int, content: String) -> Unit) {
            // 函数类型的参数现在有了名字
            println("performRequest")
        }

        val url = "http://www.jinbeen.com"
        // 可以使用API中提供的参数名字作为lambda参数的名字
        performRequest(url, { code, content -> { println(url.split('.')) } })
        performRequest(url) { code, content -> { println(url.split('.')) } }
        // 或者你可以改变参数的名字
        performRequest(url) { code, page -> { println(url.split('.')) } }


        /**-------------------- 8.1.2 调用作为参数的函数 ----------------------*/
        // 代码清单8.1 定义一个简单高阶函数
        // 定义一个函数类型的参数
        fun twoAndThree(operation: (Int, Int) -> Int) {
            // 调用函数类型的参数
            val result = operation(2, 3)
            println("The result is $result")
        }
        twoAndThree { a, b -> a + b }// The result is 5
        twoAndThree { a, b -> a * b }// The result is 6

        /*
        * filter 函数的声明,以一个判断式作为参数
        *
        *     接收者类型      参数类型      函数类型参数
        * fun String.filter(predicate: (Char)->Boolean): String
        * Char: 作为参数传递的函数的参数类型
        * Boolean: 作为参数传递的函数的返回类型
        */

        // 代码清单8.2 实现一个简单版本的filter函数
        // 检查每个字符是否满足判断式,如果满足就将字符添加到包含结果的 StringBuilder 中
        fun String.filter(predicate: (Char) -> Boolean): String {
            val sb = StringBuilder()
            for (index in 0 until length) {
                val element = get(index)
                // 调用作为参数传递给 predicate 的函数
                if (predicate(element)) sb.append(element)
            }
            return sb.toString()
        }
        // 传递一个lambda作为 predicate 参数
        println("ab1c".filter { it in 'a'..'z' })// abc


        /**-------------------- 8.1.3 在Java中使用函数 ----------------------*/
        /*kotlin类型的声明*/
        fun processTheAnswer(f: (Int) -> Int) {
            println(f(42))
        }

        /*Java8*/
        // processTheAnswer(number->number+1)

        /*旧版的Java*/
//        processTheAnswer(new Function1 < Integer, Integer > (){
//            @Override
//            public Integer invoke(Integer number){
//                System.out.println(number);
//                return number+1;
//            }
//        });

        /**-------------------- 8.1.4 函数类型的参数默认值和null值 ----------------------*/
        // 代码清单8.3 使用了硬编码toString转换的joinToString函数
        fun <T> Collection<T>.joinToString(
                separator: String = ",",
                prefix: String = "",
                postfix: String = ""
        ): String {
            val result = StringBuilder(prefix)
            for ((index, element) in this.withIndex()) {
                if (index > 0) result.append(separator)
                // 使用默认的 toString 方法将对象转换为字符串
                result.append(element)
            }
            result.append(postfix)
            return result.toString()
        }

        // 代码清单8.4 给函数类型的参数指定默认值
        fun <T> Collection<T>.joinToString2(
                separator: String = ", ",
                prefix: String = "",
                postfix: String = "",
                // 声明一个以lambda为默认值的函数类型的参数
                transForm: (T) -> String = { it.toString() }
        ): String {
            val result = StringBuilder(prefix)
            for ((index, element) in this.withIndex()) {
                if (index > 0) result.append(separator)
                // 调用作为实参传递给 transform 形参的函数
                result.append(transForm(element))
            }
            result.append(postfix)
            return result.toString()
        }

        val letters = listOf("jingbin", "Jinbeen")
        // 使用默认的转换函数
        println(letters.joinToString2())// jingbin, Jinbeen
        // 传递一个lambda作为参数
        println(letters.joinToString2 { it.toLowerCase() })// jingbin, jinbeen
        // 使用命名参数语法传递几个参数,包括一个lambda
        println(letters.joinToString2("! ", "! ",
                " ", transForm = { it.toUpperCase() }))// JINGBIN! JINBEEN


        fun foo(callback: (() -> Unit)?) {
            // 显示的检查null
            if (callback != null) {
                callback()
            }
            // 也可以这样写
            callback?.invoke()
        }

        // 代码清单8.5 使用函数类型的可空参数
        fun <T> Collection<T>.jointToString3(
                separator: String = ", ",
                prefix: String = "",
                postfix: String = "",
                // 声明一个函数类型的可空参数
                transForm: ((T) -> String)? = null): String {
            val result = StringBuilder(prefix)
            for ((index, element) in this.withIndex()) {
                if (index > 0) result.append(separator)
                // 使用安全调用语法调用函数
                // 使用Elvis运算符处理回调没有被指定的情况
                val str = transForm?.invoke(element) ?: element.toString()
                result.append(str)
            }
            result.append(postfix)
            return result.toString()
        }


        /**-------------------- 8.1.5 返回函数的函数 ----------------------*/
        // 代码清单8.6 定义一个返回函数的函数
//        enum class Delivery { STANDARD, EXPEDITED }
        class Order(val itemCount: Int)

        fun getShippingCostCalculator(
                // 声明一个返回函数的函数
                delivery: Delivery): (Order) -> Double {
            if (delivery == Delivery.EXPEDITED) {
                // 返回lambda
                return { order -> 6 + 2.1 * order.itemCount }
            }
            // 返回lambda
            return { order -> 1.2 * order.itemCount }
        }

        // 取得的是函数,将返回的函数保存在变量中
        val calculator = getShippingCostCalculator(Delivery.EXPEDITED)
        println("Shopping costs ${calculator(Order(3))}")// 12.3

        /*
        * 声明一个返回另一个函数的函数,需要指定一个函数类型作为返回类型。
        * getShippingCostCalculator返回了一个函数,这个函数以 Order 作为参数并返回一个 Double 类型的值。
        * 要返回一个函数,需要写一个 return 表达式,跟上一个 Lambda、一个成员引用,或者其他的函数类型的表达式,
        * 比如一个(函数类型的)局部变量。
        */

        // 代码清单8.7 在UI代码中定义一个返回函数的函数
        data class Person(
                val firstName: String,
                val lastName: String,
                val phoneNumber: String?
        )

        class ContactListFilters {
            var prefix: String = ""
            var onlyWithPhoneNumber: Boolean = false
            // 声明一个返回函数的函数
            fun getPredicate(): (Person) -> Boolean {
                val startsWithPrefix = { p: Person ->
                    p.firstName.startsWith(prefix) || p.lastName.startsWith(prefix)
                }
                if (!onlyWithPhoneNumber) {
                    // 返回一个函数类型的变量
                    return startsWithPrefix
                }
                // 从这个函数返回一个lambda
                return { startsWithPrefix(it) && it.phoneNumber != null }
            }
        }

        val contacts = listOf(Person("Dmitry", "bin", "188-1"),
                Person("Jin", "been", null))
        val contactListFilters = ContactListFilters()
        // 简化多次使用contactListFilters,apply会返回传入的对象
        with(contactListFilters) {
            prefix = "Dm"
            onlyWithPhoneNumber = true
        }
        // 将 getPredicate 返回的函数作为参数传递给 filter 函数
        println(contacts.filter(contactListFilters.getPredicate()))


        /**-------------------- 8.1.6 通过lambda去除重复代码 ----------------------*/
        // 代码清单8.8 定义站点访问数据
        data class SiteVisit(val path: String, val duration: Double, val os: Os)
//        enum class Os { WINDOWS, LINUX, MAC, IOS, ANDROID }

        val log = listOf(
                SiteVisit("/", 34.0, Os.WINDOWS),
                SiteVisit("/", 22.0, Os.MAC),
                SiteVisit("/login", 12.0, Os.WINDOWS),
                SiteVisit("/signup", 8.0, Os.IOS),
                SiteVisit("/", 16.3, Os.ANDROID)
        )

        // 代码清单8.9 使用硬解码的过滤器分析站点访问数据
        val average = log.filter { it.os == Os.WINDOWS }
                .map(SiteVisit::duration)
                .average()
        println(average)// 23.0

        // 代码清单8.10 用一个普通方法去除重复代码
        fun List<SiteVisit>.averageDurationFor(os: Os) =
                // 将重复代码抽取到函数中
                filter { it.os == os }.map(SiteVisit::duration).average()
        println(log.averageDurationFor(Os.WINDOWS))// 23.0
        println(log.averageDurationFor(Os.MAC))// 22.0

        // 代码清单8.11 用一个复杂的硬编码函数分析站点访问数据
        val averageMobileDuration = log.filter { it.os in setOf(Os.IOS, Os.ANDROID) }.map(SiteVisit::duration).average()
        println(averageMobileDuration)// 12.15

        // 代码清单8.12 用一个高阶函数去除重复代码
        fun List<SiteVisit>.averageDurationFor2(predicate: (SiteVisit) -> Boolean) =
                filter(predicate).map(SiteVisit::duration).average()

        println(log.averageDurationFor2 {
            it.os in setOf(Os.ANDROID, Os.IOS)
        })// 12.15
        println(log.averageDurationFor2 {
            it.os == Os.IOS && it.path == "/singup"
        })

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

代码语言:javascript
复制
/**-------------------- 8.2.1 内联函数如何运作 ----------------------*/
        // 代码清单8.13 定义一个内联函数
        // 函数用于确保一个共享资源不会并发地被多个线程访问。函数锁住一个Lock对象,执行代码块,然后释放锁。
//        inline fun <T> synchronized(lock: Lock, action: () -> T): T {
//            lock.lock()
//            try {
//                return action()
//            } finally {
//                lock.unlock()
//            }
//        }

        val l = ReentrantLock()
        synchronized(l) {}

        fun foo(l: Lock) {
            println("Before sync")
            synchronized(l) {
                println("Action")
            }
            println("After sync")
        }

        // 编译后的 foo 函数为:
        fun _foo() {
            println("Beforesync ")
            l.lock()
            try {
                println("Action")
            } finally {
                l.unlock()
            }
            println("After sync")
        }

        // 在调用内联函数的时候也可以传递函数类型的变量作为参数
        class LockOwner(val lock: Lock) {
            fun runUnderLock(body: () -> Unit) {
                synchronized(lock, body)
            }
        }

        // runUnderLock 会被编译成类似于以下函数的字节码
        class LockOwner2(val lock: Lock) {
            fun _runUnderLock(body: () -> Unit) {
                lock.lock()
                try {
                    // body 没有内联,因为在调用的地方还没有lambda
                    body()
                } finally {
                    lock.unlock()
                }
            }
        }


        /**-------------------- 8.2.2 内联函数的限制 ----------------------*/
        // 一般来说,参数如果被直接调用或者作为参数传递给另一个 inline 函数,他是可以被内联的。
//        fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
//            return TransformingSequence(this, transform)
//        }

        // 接收非内联lambda的参数,可以用 noinline 修饰符来标记它:
//        inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
//
//        }


        /**-------------------- 8.2.3 内联集合操作 ----------------------*/
        // 代码清单8.14 使用lambda过滤一个集合
        data class Person(val name: String, val age: Int)

        val people = listOf(Person("jingbin", 28), Person("mayun", 44))
        println(people.filter { it.age < 30 })// [Person(name=jingbin, age=28)]

        // 代码清单8.15 手动过滤一个集合
        val result = mutableListOf<Person>()
        for (person in result) {
            if (person.age < 30) {
                result.add(person)
            }
        }
        println(result)// [Person(name=jingbin, age=28)]

        /*
        *  filter被声明成了内联函数,以上两个代码产生的字节码其实是一样的。
        */
        println(people.filter { it.age < 30 }.map(Person::name))// jingbin

        /**
         * filter 和 map 都是内联函数。
         * filter 创建了一个中间集合来保存列表过滤的结果,然后map函数生成的代码会读取这个这个结果。
         * 如果有大量的元素处理,中间的开销还有问题,这时可以在调用链的后面加上一个 asSequence 调用,
         * 用序列来替代集合,大量的数据处理时加上即可。
         */
        // 用序列替代集合?
        println(people.filter { it.age < 30 }.map(Person::name).asSequence())// jingbin
        println(people.asSequence().filter { it.age < 30 }.map(Person::name))// jingbin


        /**-------------------- 8.2.4 决定何时将函数声明成内联 ----------------------*/
        /*
        * 使用 inline 关键字只能提高带有lambda参数的函数的性能,其他的情况需要额外度量和研究。
        * Kotlin标准库中的内联函数总是很小的。
        */

        /**-------------------- 8.2.5 使用内联lambda管理资源----------------------*/

//        val l :Lock = ...
        // 在加锁的情况下执行指定的操作
//        l.withLock {  }

        // withLock 函数的定义:
        // action: 需要加锁的地方被抽取到一个独立的方法中
        fun <T> Lock.withLock(action: () -> T): T {
            lock()
            try {
                return action()
            } finally {
                unlock()
            }
        }

        // 代码清单8.16 在Java中使用 try-with-resource
        //    static String readFirstLineFromFile(String path) throws IOException {
        //        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(path))) {
        //            return bufferedReader.readLine();
        //        }
        //    }

        // 代码清单8.17 使用use函数作资源管理。[不会引发任何的性能开销]
        fun readFirstLineFromFile(path: String): String {
            // 构建 BufferedReader ,调用 use 函数,传递一个 lambda 执行文件操作
            BufferedReader(FileReader(path)).use { br ->
                // 从函数中返回文件的一行
                return br.readLine()
            }
        }

8.3 高阶函数中的控制流

代码语言:javascript
复制
/**-------------------- 8.3.1 lambda 中的返回语句:从一个封闭的函数返回 ----------------------*/
        // 代码清单8.18 在一个普通循环中使用return
        data class Person(val name: String, val age: Int)

        val people = listOf(Person("Alice", 29), Person("Bob", 31))
        fun lookForAlice(people: List<Person>) {
            for (person in people) {
                if (person.name == "Alice") {
                    println("Found!")
                    return
                }
            }
            // 如果 people 中没有 Alice ,这一行就会被打印出来
            println("Alice is no found")
        }
        lookForAlice(people)// Found!

        // 代码清单8.19 在传递给 forEach 的lambda中使用return
        fun lookForAlice2(people: List<Person>) {
            people.forEach {
                if (it.name == "Alice") {
                    // 和 8.18 中一样返回
                    println("Found!!")
                    return
                }
            }
            println("Alice is no found")
        }
        // 只有在以lambda作为参数的函数是内联函数的时候 才能从更外层的函数返回。


        /**-------------------- 8.3.2 从lambda返回: 使用标签返回 ----------------------*/
        /*
        * 也可以在 lambda 表达式中使用局部返回。lambda中的局部返回跟for循环中的break表达式类似。
        * 要区分局部返回和非局部返回,要用到标签。
        */

        // 代码清单8.20 用一个标签实现局部返回
        fun lookForAlice3(people: List<Person>) {
            // 给 lambda 表达式加上标签
            people.forEach lable@{
                // return@lable 引用了这个标签
                if (it.name == "Alice") return@lable
            }
            // 这一行总是会打印出来
            println("Alice might be somewhere")
        }
        lookForAlice3(people)// Alice might be somewhere

        /*
        * people.forEach lable@{
        *        if (it.name == "Alice") return@lable
        * }
        *   lable@ lambda标签
        *   @lable 返回表达式标签
        */

        // 代码清单8.21 用函数名作为 return 标签
        fun lookForAlice4(people: List<Person>) {
            people.forEach {
                // return@forEach 从lambda表达式返回
                if (it.name == "Alice") return@forEach
            }
            println("Alice might be somewhere")
        }

        // 带标签的 this 表达式
        // 这个 lambda 的隐式接收者可以通过 this@sb 访问
        val apply = StringBuilder().apply sb@{
            listOf(1, 2, 3).apply {
                // this 指向作用域内最近的隐式接收者。
                // 所有隐式接收者都可以被访问,外层的接收者通过显示的标签访问
                this@sb.append(this.toString())
            }
        }
        println(apply)// [1,2,3]


        /**-------------------- 8.3.3 匿名函数:默认使用局部返回 ----------------------*/
        // 代码清单8.22 在匿名函数中使用 return
        fun lookForAlice5(people: List<Person>) {
            // 使用匿名函数取代 lambda 表达式
            people.forEach(fun(person) {
                // return 指向最近的函数:一个匿名函数
                if (person.name == "Alice") return
                println("${person.name} is not Alice")
            })
        }
        println(lookForAlice5(people))

        // 代码清单8.23 在filter中使用匿名函数
        people.filter(fun(person): Boolean {
            return person.age < 30
        })

        // 代码清单8.24 使用表达式体匿名函数
        people.filter(fun(person) = person.age < 30)
        // return 从最近的使用 fun 关键字声明的函数返回。
        // 8.22 中返回的是 fun(person)

        // 以下返回的是 fun lookForAlice6
        fun lookForAlice6(people: List<Person>) {
            people.forEach {
                if (it.name == "Alice") return
            }
        }

总结

  • 函数类型可以让你声明一个持有函数引用的变量、参数或者函数返回值。
  • 高阶函数以其他函数作为参数或者返回值。可以用函数类型作为函数参数或者返回值的类型来创建这样的函数。
  • 内联函数被编译以后,它的字节码连同传递给它的 lambda 的字节码会被插入到调用函数的代码中,这使得函数调用相比于直接编写相同的代码,不会产生额外的运行时开销。
  • 高阶函数促进了一个组件内的不同部分的代码重用,也可以让你构建功能强大的通用库。
  • 内联函数可以让你使用非局部返回一一在 lambda 中从包含函数返回的返回表达式。
  • 匿名函数给 lambda 表达式提供了另一种可选的语法,用不同的规则来解析 return 表达式。可以在需要编写有多个退出点的代码块的时候使用它们。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 8.1 声明高阶函数
  • 8.2 内联函数:消除lambda带来的运行时开销
  • 8.3 高阶函数中的控制流
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档