Kotlin学习笔记 第三章 函数 高阶函数 lambda表达式 内联函数

参考链接

Kotlin官方文档

https://kotlinlang.org/docs/home.html

中文网站

https://www.kotlincn.net/docs/reference/properties.html

本系列为参考Kotlin中文文档

https://download.csdn.net/download/u011109881/21418027

整理的笔记 

pdf也可以在这里下载

https://www.kotlincn.net/docs/kotlin-docs.pdf

大部分示例来自bilibili Kotlin语言深入解析 张龙老师的视频

Part1

知识点

1 函数的默认参数

2 函数默认值在函数覆盖时的注意点

3 无默认值参数在有默认值参数

4 函数最后一个参数是一个函数

5 可变参数vararg

6 * spread operator 分散运算符

7 具名参数

8 函数简写

9 单表达式函数

10 显式返回类型

11 可变参数的位置

笔记

// 函数
fun main() {

    // 1 函数的默认参数
    test()
    test(2)
    // b 是具名参数
    // 显示指定参数名
    test(b = 3)
    test(3, 2)
    test(a = 3)
    // 2 函数默认值在函数覆盖时的注意点

    // 3 无默认值参数在有默认值参数
    // 这种情况如果想省略参数 只能通过具名参数的方式来使用
    test2(1, 2) // 要么不省略任何参数
    test2(b = 3) // 要么 只能使用具名参数

    // 4 函数最后一个参数是一个函数
    // 调用已有方法
    test3(2, 3, ::test)
    // 另外新写方法
    test3(4, 5, { a, b -> println(a - b) })
    // 将上面的方法缩写
    // 即 函数最后一个参数是一个lambda表达式 可以将其移动到小括弧外部
    test3(4, 5) { a, b -> println(a - b) }
    // 省略一个参数
    test3(4) { a, b -> println(a - b) }
    test3(b = 4) { a, b -> println(a - b) }
    // 调用函数时 如果同时使用了位置参数和具名参数,那么所有位置参数必须要位于第一个具名参数之前
    // 例如 foo(1,x=2) OK foo(x=2,2) NG

    // 5 可变参数vararg
    test4("a", "b", "c")
    test4New("a", "b", "c")

    // 6 * spread operator 分散运算符
    val arrays:Array = arrayOf("str1", "str2", "str3")
    // 不能直接传入string数组 Kotlin会以为只有一个参数  Type mismatch. Required: String Found: Array
    // test4(strings) // 报错
    // 需要使用 * 字符拆分符 将string数组拆分为多个参数
    test4(/*strings =*/*arrays)
    test4(/*strings =*/*arrayOf("a","b"))
    test4("a")

    // 7 具名参数
    // 在Kotlin中调用Java代码时不能使用具名参数 因为Java字节码不总是会保留参数名信息
    // 即 具名参数不能用在Kotlin调用Java代码时

    // 8 函数简写
    myPrintNew("======")

    // 9 单表达式函数
    // 当函数返回单个表达式时,可以省略花括号并且在 = 符号之后指定代码体即可
    add(1,2)

    // 10 显式返回类型
    // 拥有方法体或者说块体的方法(即花括号)需要定义显式的返回类型 除非该方法返回Unit
    // Kotlin 不会推断拥有方法体或者说块体的方法的返回值类型 因为这种函数可能有非常复杂的控制流程
    // 对于阅读代码的人和编译器来说 返回类型不是那么明显了 因此Kotlin统一不对所有显示返回类型的方法做类型推断
    // 例如addNew就是一个 显示返回类型的方法

    // 11 可变参数的位置
    // 一个方法中 只允许有一个参数为可变参数,通常是最后一个参数 但是有例外
    // 如果vararg不是最后一个参数 那么可变参数后面的参数需要通过具名参数进行传递
    // 如果其后的参数是函数类型 可以通过圆括号外部传递lambda表达式来实现
    println(convert2List("hello", "world"))
    val elements = arrayOf("welcome", "kotlin", "test")
    println(convert2List("1", "2", elements))
    println(convert2List("1", "2", *elements))//注意和上面区别
    println(convert2ListNew("hello", "world", a = "1", b = "2"))
    println(convert2ListNew2("hello", "world") { a, b ->
        println("a is $a b is $b")
    })
    println(convert2ListNew2("hello") { a, b ->
        println("a is $a b is $b")
    })
}

// 11 可变参数的补充
fun  convert2List(vararg element: T): List {
    val res = ArrayList()
    element.forEach { res.add(it) }
    return res
}

// 11 可变参数的补充 可变参数后面还有其他参数
fun  convert2ListNew(vararg element: T, a: T, b: T): List {
    val res = ArrayList()
    element.forEach { res.add(it) }
    res.add(a)
    res.add(b)
    return res
}

// 11 可变参数的补充 可变参数后面的参数是一个函数
fun  convert2ListNew2(vararg element: T, myPrint: (x: T, y: T) -> Unit): List {
    val res = ArrayList()
    element.forEach { res.add(it) }
    if (res.size >= 2) {
        myPrint(res[0], res[1])
    } else {
        println("list too short")
    }
    return res
}

// 9 单表达式函数
// 函数的返回类型如果可以通过类型推断判断出来 那么返回类型可以省略
fun add(a: Int, b: Int) = a + b

fun addNew(a: Int, b: Int): Int {
    return a + b
}

// 8 函数简写
// 当返回值为Unit时可以简写
fun myPrint(name: String) {
    println(name)
}

// 完整写法
fun myPrintNew(name: String): Unit {
    println(name)
    return Unit
}

// 5 可变参数vararg
fun test4(vararg strings: String) {// strings 属于string数组类型 // 注意string数组和Array 不一样
    println(strings.javaClass)
    strings.forEach { println(it) }
}

fun test4New(vararg strings: String) {
    println(strings.javaClass)
    strings.forEach { a ->
        println(a)// 当只有一个参数时 可以简写为上面那种形式 println(it)
    }
}

// 4 函数最后一个参数是一个函数
// 如下 第三个参数是一个接受2个参数 返回值为空的函数
fun test3(a: Int = 1, b: Int = 2, compute: (x: Int, y: Int) -> Unit) {
    compute(a, b)
}

// 3 无默认值参数在有默认值参数
fun test2(a: Int = 0, b: Int) = println(a + b)

// 2 函数默认值在函数覆盖时的注意点 start
open class A {
    open fun function(a: Int, b: Int = 4) = a + b
}

class B : A() {
    // 对于重写的方法来说 子类重写的方法会使用父类方法的参数默认值 即使父类方法的参数没有默认值
    // 子类也不能拥有自己参数的默认值
    // 个人理解 这里Kotlin的设计是为了避免函数默认值混乱
    // An overriding function is not allowed to specify default values for its parameters
    override fun function(a: Int, b: Int) = a + b
}
// 2 函数默认值在函数覆盖时的注意点 end

// 1 函数的默认参数
fun test(a: Int = 0, b: Int = 1) {
    println(a - b)
}

// 如果函数体只有一句话 可以去掉{} 写成如下形式
fun testNew(a: Int = 0, b: Int = 1) = println(a - b)

class D0401Functions {
}

Part2

知识点

1 中缀符号 (infix notation)

2 内联函数

3 高阶函数(high-order function)和 Lambda 表达式

笔记

fun main() {
    // 1 中缀符号 (infix notation)
    // 具体作用主要是 忽略该调用的点与圆括号
    // 函数定义成中缀函数有3个条件
    // a 是成员函数或扩展函数 (依赖于某一个类)
    // b 只有一个参数 且没有默认值(不可以是可变参数)
    // c 使用infix修饰
    val infixTest = InfixTest(2)
    println(infixTest.add(5))
    println(infixTest add 5) //中缀表达式的写法

    /**
     * 2 内联函数
     * 2.1 内联函数的目的
     * 以下为个人理解
     * 函数之间的调用会有函数堆栈 进入下一个函数前 当前函数的信息会以存入堆栈 知道从下一个函数返回才会取出
     * 因此 函数调用层级越高 会导致调用堆栈异常庞大 在递归函数中如果发生无法终止递归的情况 会一直运行直到堆栈溢出
     * 内联函数的目的是为了避免函数堆栈过大设计出来的
     * 调用到内联函数时 会将该函数复制到调用处 这样 在同一个函数里 就不会创建创建函数调用堆栈存放数据了
     * 2.2 内联函数的缺点
     * 以下为个人理解
     * 虽然内联函数一定程度上降低了调用堆栈的高度 但是 如果内联函数被调用的次数太多 那么就需要不断的拷贝
     * 这种操作消耗的资源也不见得很小 所以 内联函数的作用其实有限
     * 2.3 内联函数的示例
     * 通过反编译kotlin为Java 我们可以轻易看出区别
     * 或者通过 javap xx.D0401Functions2  javap -c xx.D0401Functions2 反编译出字节码 也可以看出区别
     */
    println(myInlineFun(1, 2))
    println(myNormalFun(1, 2))
    println("=====2 end======")

    /**
     * 3 高阶函数(high-order function)和 Lambda 表达式
     * 一个函数 如果它接受一个函数作为参数或者返回一个函数 那么这就是高阶函数
     * lambda 表达式的要求
     * a lambda表达式被花括号包围
     * b 参数位于 —> 之前 (参数类型可以省略)
     * c 执行体位于 -> 之后
     * 在kotlin中 如果参数的最后一个参数是一个函数 那么可以将lambda表达式作为实参传递 并且 放在圆括号之外的一对花括号中
     */
    println(oneParameter(2))
    println("===== 3 end======")
}

// lambda 表达式示例
val multiply: (Int, Int) -> Int = { a: Int, b: Int -> a * b } // 最完整写法
val multiply2: (Int, Int) -> Int = { a, b -> a * b } // 简写1
val multiply3 = { a: Int, b: Int -> a * b } // 简写2
val myAction = { println("hello world") } // 无参的lambda表达式
val myAction2: () -> Unit = { println("hello world") } // 完整写法
val myReturnNull: (Int, Int) -> Int? = { a, b ->
    if (a + b > 0) {
        a + b
    } else {
        null
    }
}

// 下划线 变量占位符 如果变量在函数中没有使用 可以使用变量占位符代替
val myReturnNull2: (Int, Int) -> Int? = { _, _ -> null }

// 当只有一个参数时 可以用it代替
val oneParameter: (Int) -> Int = { it -> it + 1 }

// 整个函数可能为空
val functionMayNull: ((Int, Int) -> Int)? = null


// 2 内联函数对比普通函数
inline fun myInlineFun(a: Int, b: Int): Int = a + b
fun myNormalFun(a: Int, b: Int): Int = a + b


// 1 中缀函数demo
class InfixTest(val a: Int) {
    infix fun add(b: Int) = this.a + b
}

class D0401Functions2

Part3

知识点

1 高阶函数

2 高阶函数的应用

3 高阶函数的应用2

4 高阶函数的应用3

5 lambda表达式的返回值

6 匿名函数

7 闭包

8 带有接收者的函数字面值

9 函数字面值结合匿名函数

10 函数字面值扩展

笔记

fun main() {
    /**
     * 1 高阶函数
     */
    myCalculate(1, 3, { a, b -> a + b })
    myCalculate(1, 3) { a, b -> a + b }
    myCalculate(1, 3, ::myAdd)
    println("====== 1 end =======")

    // 2 高阶函数的应用
    // 过滤掉字符串中所有非字母字符
    println("123hellow123".filter { it.isLetter() })
    println("123ffff".filter { a -> a.isLetter() })
    println("he1llo123".filter2 { a -> a.isLetter() })
    println("====== 2 end =======")

    // 3 高阶函数的应用2
    // 筛选字符串数组中包含字符H h的字符串
    val strings = arrayOf("hello", "world", "kotlin", "hi")
    // 复杂写法
    filterStrings(strings) { string -> string.contains("H") || string.contains("h") }
    // 简化写法
    strings.filter { it.contains("h", ignoreCase = true) }.forEach { println(it) }
    println("-----")
    // 筛选字符串数组中字符串长度=5的
    // 复杂
    strings.filter { string -> string.length == 5 }.forEach { string -> println(string) }
    // 简化
    strings.filter { it.length == 5 }.forEach { println(it) }
    println("====== 3 end =======")

    // 4 高阶函数的应用3
    // 找出字符串数组中包含D或d的字符串 将其改为大写输出
    strings.filter { it.contains("d", true) }.forEach { println(it.uppercase(Locale.getDefault())) }
    println("====== 4 end =======")

    /**
     * 5 lambda表达式的返回值
     * 在默认情况下 lambda表达式的最后一句话会作为该lambda表达式的返回值
     * 我们可以全限定的return语法显示从lambda表达式返回值
     */
    strings.filter {
        val needFilter = it.length == 5
        needFilter //lambda表达式的默认返回值
    }
    // 什么是 全限定的return语法
    strings.filter {
        val needFilter = it.length == 5
        return@filter needFilter // 全限定的return语法 会使用到标签 用法为return@方法名 用来表示lambda的返回
        // 此种用法可以使用 但是通常不用 可读性不高
    }
    println("====== 5 end =======")

    /**
     * 6 匿名函数
     * 匿名函数与普通函数的区别
     * 匿名函数的参数类型如果可以推断出来 可以省略
     * 匿名函数不能之间定义在顶层(因为那样就无法调用了)
     * 匿名函数所有参数必须在圆括号中 参数是lambda表达式也一样
     * 匿名函数主要可以代替lambda表达式 (实际意义不大)
     * 匿名函数的return是返回匿名函数本身 而lambda表达式则不同 之间return表示返回到外层函数
     * 匿名函数的使用开起来不如lambda 了解即可
     */
    // 定义了两个无法使用的匿名函数
    fun(x: Int, y: Int) = x + y
    fun(x: Int, y: Int): Int {
        return x + y
    }

    val strings2 = arrayOf("hello", "world", "kotlin", "hi")
    // lambda
    strings2.filter { it.length == 5 }.forEach { println(it) }
    // 匿名函数代替lambda
    strings2.filter(fun(item: String): Boolean { return item.length == 5 }).forEach(fun(item) { println(item) })
    println("====== 6 end =======")
    /**
     * 7 闭包
     * Lambda 表达式或者匿名函数(以及局部函数和对象表达式) 可以访问其闭包 ,
     * 即在外部作用域中声明的变量。 在 lambda 表达式中可以修改闭包中捕获的变量
     * 如下 forEach中可以访问闭包中的 sum
     */

    var sum = ""
    val strings3 = arrayOf("hello", "world", "kotlin", "hi")
    strings3.filter { it.contains("h") }.forEach { sum += it }
    println(sum)
    println("====== 7 end =======")

    /**
     * 8 带有接收者的函数字面值
     * 这个功能及其类似扩展函数
     * 带有接收者的函数类型,例如 A.(B) -> C,可以用特殊形式的函数字面值实例化—— 带有接收者的函数字面值。
     * 如上所述,Kotlin 提供了调用带有接收者(提供接收者对象)的函数类型实例的能力。
     * 在这样的函数字面值内部,传给调用的接收者对象成为隐式的this,以便访问接收者对象的成员而无需任何额外的限定符,亦可使用this表达式访问接收者对象。
     * 这种行为与扩展函数类似,扩展函数也允许在函数体内部访问接收者对象的成员。
     *
     * 下面的变量是函数类型 Int的类似于扩展函数 接受一个int参数 返回一个int类型 {}里为方法体
     * 个人理解:
     * 接收者类型为Int 它在点号前面
     * 点号后面是函数字面值
     * 在函数内部可以使用this代表函数调用者
     */
    val subtract: Int.(other: Int) -> Int = { other -> this - other }
    // 等价于如下
    val subtract2: Int.(other: Int) -> Int = { this - it }
    println(3.subtract(4))
    println("====== 8 end =======")

    /**
     * 9 函数字面值结合匿名函数
     * 匿名函数语法允许你直接指定函数字面值的接收者类型。 如果你需要使用带接收者的函数类型声明一个变量,并在之后使用它,这将非常有用 -- 来自官网
     *
     */
    // 使用匿名函数的case 单表达式
    val sum2: Int.(Int) -> Int = fun Int.(other: Int): Int = this + other
    // 使用匿名函数的case 变体
    val sum3: Int.(Int) -> Int = fun Int.(other: Int): Int { return this + other }
    // 使用lambda表达式的case
    // 对比lambda的形式 完全不知道 “这将非常有用” 有用在哪?
    val sum4: Int.(Int) -> Int = { this + it }
    println(1.sum2(2))
    println(1.sum4(2))
    println("====== 9 end =======")

    /**
     * 10 函数字面值扩展
     * 带有接收者类型的函数的非字面值 可以作为参数传递 前提是所需要接受函数的地方应该有一个接收者类型的参数
     * 类型 String.(Int) -> Boolean 等价于 (String,Int) -> Boolean
     */
    // myEquals是一个函数 并且使用了带有接收者的函数字面值 是String的一个方法 方法体
    // toIntOrNull是string的一个方法 当前如果可以转为int则返回其int值否则返回null
    val myEquals: String.(Int) -> Boolean = { it -> this.toIntOrNull() == it }
    println("456".myEquals(456))
    println("156".myEquals(456))
    // myTestEquals 是一个方法 接受3个参数 第一个参数是个方法,接受string和int参数 返回Boolean值
    fun myTestEquals(op: (String, Int) -> Boolean, a: String, b: Int) = println(op(a, b))
    myTestEquals(myEquals, "456", 456)
    myTestEquals(myEquals, "156", 456)
    // 可以看到myTestEquals第一个参数明明是(String, Int) -> Boolean 却可以将myEquals传入myTestEquals
    // 可见String.(Int) -> Boolean 等价于 (String,Int) -> Boolean
    println("====== 10 end =======")
}

// 3 高阶函数的应用2
fun filterStrings(strings: Array, filter: (String) -> Boolean) {
    for (string in strings) {
        if (filter(string)) {
            println(string)
        }
    }
}

// 2 高阶函数的应用 start
fun String.filter(predicate: (Char) -> Boolean): String {
    val buffer = StringBuffer()
    for ((index, chars) in this.withIndex()) {
        if (predicate(chars)) {
            buffer.append(chars)
        }
    }
    return buffer.toString()
}

fun String.filter2(predicate: (Char) -> Boolean): String {
    val buffer = StringBuffer()
    for (index in this.indices) {
        if (predicate(this[index])) {
            println("[$index] is letter")
            buffer.append(this[index])
        } else {
            println("[$index] not letter")
        }
    }
    return buffer.toString()
}
// 2 高阶函数的应用 end

/**
 * 1 高阶函数示例
 * 该函数接受三个参数 第三个参数为一个名为calculate的函数
 * calculate函数实际只是个形参 规定了外部传入的函数接受2个int返回一个Int
 * 具体算法由外部决定
 */
fun myCalculate(a: Int, b: Int, calculate: (Int, Int) -> Int): Int {
    return calculate(a, b)
}

fun myAdd(a: Int, b: Int): Int {
    return a + b
}

class D0401Functions3

你可能感兴趣的:(Kotlin,kotlin)