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

本章内容包括:

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

8.1 声明高阶函数


         // 高阶函数就是以另一个函数作为参数或者返回值的函数。
        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带来的运行时开销


/**-------------------- 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 高阶函数中的控制流


/**-------------------- 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 表达式。可以在需要编写有多个退出点的代码块的时候使用它们。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341

推荐阅读更多精彩内容