kotlin 之函数进阶

高阶函数

​ 参数类型包含了函数,或者返回类型为函数

fun needsFunction(block: () -> Unit) {
	//调用函数
    block()
}

fun returnsFunction(): () -> Long {
	//返回一个函数
    return { System.currentTimeMillis() }
}

内联函数

​ 当使用 Lambad 表达式时,他会被编译为一个匿名类。这表示每调用一次 lambad ,就会有一个额外的类被创建,这会带来运行的额外开销,导致 lambad 比使用一个直接执行相同代码的函数效率更低。

​ 如果使用 inline 标记一个函数,这个函数被调用的时候编译器并不会生成函数的调用代码。而是 使用函数实现的真实代码替换每一次函数的调用

​ 如下:

fun main() {
    cost {
        println("Hello")
    }
}

// 添加 inline 即为内联函数
inline fun cost(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    println(System.currentTimeMillis() - start)
}

​ 反编译后如下

public static final void main() {
      int $i$f$cost = false;
      long start$iv = System.currentTimeMillis();
      int var3 = false;
      String var4 = "Hello";
      boolean var5 = false;
      System.out.println(var4);
      long var6 = System.currentTimeMillis() - start$iv;
      var5 = false;
      System.out.println(var6);
   }

​ Lambad 表达式和 cost 的实现部分都被内联了。看起来是调用了 cost ,实际上是函数本身被内联到了调用处。

​ 内联:

​ 1,函数本身被内联到调用处

​ 2,函数的函数参数被内联到调用处

内联函数的 return

val list = listOf<Int>(1, 2, 3, 4)
    list.forEach {
        if (it == 3) {
            //类似于 continue,退出本次
            return@forEach
        }
        println(it)
    }

non-local return

fun main() {
    //内联函数
    cost {
        println("Hello")
        return
    }
    println("not fond!")
}

inline fun cost(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    println(System.currentTimeMillis() - start)
}
//Hello

如果在 lambad 中使用 return 关键字,他会从调用处返回,而并非是lambda 中返回。因为这是一个内联函数,表达式中的内容相当于被提取到了调用的地方进行执行,所以就会在 main 中直接 return。

从 lambda 中返回:

fun main() {
    //内联函数
    cost label@{
        println("Hello")
        return@label
    }
    println("not fond!")
}
//Hello
//1
//not fond!

增加 label 标签后,就可以局部返回。


//定义 内联函数 添加 inline 即为内联函数
inline fun cost(crossinline block: () -> Unit): Runnable {
    return object : Runnable {
        override fun run() {
            block() //报错,因为 block 的调用处与定义处不在同一个调用上下文
            //添加 crossinline 关键字即可解决 ,禁止 non-local return
            //添加 noinlin 也可以解决,只不过 内联函数就没有意义了。
        }
    }
}

内联属性

var pocket: Double = 0.0
var money: Double
    inline get() {
        return pocket
    }
    inline set(value) {
        pocket = value
    }

和 内联函数一样,在调用 money 的时候会执行在调用的地方

内联函数的限制

1,public /protected 的内联方法只能访问对应类的 public 成员

2,内联函数的内联函数参数不能存储(赋值给变量)

3,内联函数的内联函数参数只能传递给其他内联函数参数

几个有用的函数

函数名 介绍 推荐指数
let val r = X.let{x -> R} ★★★
run val r = X.run{this.X ->R}
also val x = X.also{x -> Unit} ★★★
apply val x = X.apply{this:X -> Unit}
use val r = Closeable.use{c -> R} ★★★

​ let / run :r = lambda 表达式的返回值

​ alse / apply : x = receiver ,也就是当前的对象

​ use :有些需要关闭的代码,如流,等可以在 Closeable 中执行,执行完后会自动关闭

class Person(var name: String, var age: Int)

fun main() {

    val person = Person("benny", 20)

    var y = person.let(::println)
    var x = person.run(::println)

    person.also {
        it.name = "张三"
    }

    person.apply {
        name = "李四"
    }

    File("build.gradle").inputStream().reader().buffered().use {
        //内部做了异常处理,流关闭。点击 use 查看源码
        println(it.readLine())
    }
}

集合变换与序列

集合的映射操作

函数名 说明
filter 保留满足的元素
map 集合中的所有元素 — 映射到其他元素构成新的集合
flatMap 集合中的所有元素 — 映射到新的集合并合并这些集合得到新的集合
fun main() {

    val list = listOf<Int>(1, 2, 3, 4, 5)
    
//    集合的映射操作
//    filter
	
    list.filter { it % 2 == 0 }	//2,4
    list.asSequence().filter { it % 2 == 0 }//2,4

//	   map
    val m1 = list.map { it * 2 + 1 }        // 3,5,7,9,11
    val m2 = list.asSequence().map { it * 2 + 1 } // 3,5,7,9,11
    
//		flatmap
//		将一个元素映射成一个集合,再把所有的集合全部拼成一个新的集合
    val f1 = list.flatMap {
        //返回一个范围,将序列映射成一个集合
        0 until it
    }
    // 0 0 1 0 1 2 0 1 2 3 0 1 2 3 4

}

Kt 中的懒汉序列

fun main() {

    val list = listOf<Int>(1, 2, 3, 4, 5)
     //懒序列 asSequence
    list.asSequence()
        .filter {
            println("filter: $it")
            it % 2 == 0
        }.map {
            println("map: $it")
            it * 2 + 1
        }.forEach {
            println("forEach: $it")
        }
}
//结果
filter: 1
filter: 2
map: 2
forEach: 5
filter: 3
filter: 4
map: 4
forEach: 9
filter: 5

​ 懒序列是将集合中的元素一个个的往下执行,首先是 1,it % 2 条件不满足,接着是 2,满足,然后才会到 map 中打印出来接着变成5,然后到 forEach 中打印5。2 完了之后就是3,又从 filter 开始。一直到最后一个元素。

​ **注意:**懒汉序列中,如果不写 forEach ,上面的 filter 和 map 都不会执行!

Kt 中恶汉序列

	list
        .filter {
            println("filter: $it")
            it % 2 == 0
        }.map {
            println("map: $it")
            it * 2 + 1
        }.forEach {
            println("forEach: $it")
        }
//结果
filter: 1
filter: 2
filter: 3
filter: 4
filter: 5
map: 2
map: 4
forEach: 5
forEach: 9

​ 结果很明显

集合的聚合操作

函数名 说明
sum 所有元素求和
reduce 将元素依次按规矩聚合,结果与元素类型一致
fold 给定初始化值,将元素按规矩聚合,结果与初始化类型一致

fold

	//    fold
    val f = list.fold(String()) { s: String, i: Int ->
        s + i
    }
    println(f) 	//12345

在这里插入图片描述

​ 上面是执行过程

​ 会有一个初始值,就是上面的 s ,然后和每一个元素进行拼接,最终将 s 返回。

reduce

​ 少了初始值

	val r = list.reduce() { acc, i ->
        acc + i
    }
    println(r)	//15

​ 可以看到,少了一个参数,不能指定类型,这里 acc 是 Int 类型

sum

val s = list.sum()

zip

fun main() {

    val list = listOf<Int>(1, 2, 3, 4, 5)
    val array = arrayOf(2, 2)
    val z = list.zip(array) { a: Int, b: Int ->
        a * b
    }
    z.forEach {
        println(it)
    }	//2,4
}

​ 结果是 2,4。为啥呢?看一哈源码就明白了:

public inline fun <T, R, V> Iterable<T>.zip(other: Array<out R>, transform: (a: T, b: R) -> V): List<V> {
	//拿到数组的长度
    val arraySize = other.size
    //创建一个新的集合
    val list = ArrayList<V>(minOf(collectionSizeOrDefault(10), arraySize))
    var i = 0
    //遍历集合,也就是调用这个方法的集合
    for (element in this) {
    	//i 必须 小于数组的长度
        if (i >= arraySize) break
        //这里调用了我们传入的函数,传入当前元素,和 数组中的元素
        list.add(transform(element, other[i++]))
    }
    //返回一个集合
    return list
}

​ 是不是非常清晰呢?

SAM 转换

SAM: Single Abstract Method ,一个抽象方法

JAVA:一个参数类型为 只有一个方法的接口的方法调用时可以使用 Lambda 表达式做转换参数

Kotlin:一个参数类型为 只有一个方法的 Java 接口的 Java 方法 ,调用时可用 Lambda 表达式作为转换参数。

例如:

一个 Java 的接口 和 类

interface Runnable {
    void run(int x);
}

public class Main {
    public void execute(Runnable runnable) {

    }
}

在 java 中调用可以使用 Lambda 做转换

在 Kt 中调用如下

main.execute { x ->

}

在 Kt 中调用 execute,可以传入一个 Lambda,而不需要 Runnable 的实例,因为在 Kt 看来,这个方法本身是可以接受两种类型的:Runnable 和 (x:Int)->Unit,前面任何一个条件不成立,这个结果就不成立,例如,Runable 是一个 Kotlin 类,或者 execute 是一个 Kotlin 方法都不可以

如果在 Kt 中定义了一个接口和 函数,应该怎么传递呢:

fun setR(r: R) {

}

interface R {
    fun run()
}

fun main(){
    //object :R 表示创建一个匿名内部类
	 setR(object :R{
        override fun run() {

        }
    })
}

​ 通过上面这种方法即可。

Java 的 Lambda 是假的,本质上就是 SAM 转换。

Kotlin 的 Lambda 是真的,只是支持了 SAM 转换,所以在调用 java 方法的时候可以 使用 Lambda。

SAM 转换的坑

看例子:

// JAVA
public class Main {

    interface Runnable {
        void run(int x);
    }

    List<Runnable> list = new ArrayList<>();

    public void add(Runnable runnable) {
        list.add(runnable);
    }

    public void remove(Runnable runnable) {
        list.remove(runnable);
    }

    public int size() {
        return list.size();
    }
}

fun main() {

    val main = Main()
	//添加 Runnable
    // 使用 SAM 转换,其实就是创建了一个 匿名内部类
    main.add {
        println(it)
    }
    //上面这种就是如下的形式
 //    main.add(object :Main.Runnable{
//        override fun run(x: Int) {
//            {
//                println(x)
//            }()
//        }
//    })
    println(main.size())
	//删除
    main.remove {
        println(it)
    }
    println(main.size())
}

结果:1,1。没有删除掉,因为 Remove 删除的是一个新的 Lambda。不可能删除

也有一些人发现了这个问题,采用如下解决方法:

fun main() {

    val main = Main()

    val r = { i: Int ->
        println(i)
    }

    main.add(r)

    println(main.size())

    main.remove(r)
    println(main.size())
}

但是这种真的有用吗?

结果还是 :1,1

其实最终调用还是如下:

//还是一个匿名内部类
main.add(object : Main.Runnable {
        override fun run(x: Int) {
            r.invoke(x)
        }
})

解决

 val r = object : Main.Runnable {
        override fun run(x: Int) {
            println(x)
        }
    }

​ 直接定义一个匿名内部类,然后添加删除即可 。


案例

**练习1:**找出一个文件中每个字符出现多少次

fun main() {
    File("build.gradle").readText() //读文件
        .toCharArray()//得到字符数组
        .filterNot(Char::isWhitespace) //过滤空白
        .groupBy { it } //按照 it 分组,分组后 key 是 char,值是对应的 list
        // map,将元素映射为Pair
        .map {
            it.key to it.value.size
        }
        //打印
        .let {
            println(it)
        }
}

练习2:HTML DSL

interface Node {
    fun render(): String
}

class StringNode(val content: String) : Node {
    override fun render(): String {
        return content
    }

}

class BlockNode(val name: String) : Node {


    val children = ArrayList<Node>()
    val properties = HashMap<String, Any>()

    override fun render(): String {
        return """<$name${properties.map { "${it.key}='${it.value}'" }.joinToString(" ")}>${children.joinToString("") { it.render() }}</$name>"""
    }

    operator fun String.invoke(block: BlockNode.() -> Unit): BlockNode {
        val node = BlockNode(this)
        node.block()
        this@BlockNode.children += node
        return node
    }

    operator fun String.invoke(value: Any) {
        this@BlockNode.properties[this] = value
    }

    operator fun String.unaryPlus() {
        this@BlockNode.children += StringNode(this)
    }
}

/**
 * 接收一个 BlockNode 的扩展函数
 * 如果传入 Lambda,这个 Lambda 中就可以调用 BlockNode 中的成员函数了
 */
fun html(block: BlockNode.() -> Unit): BlockNode {
    val html = BlockNode("html")
    //调用传入的 lambda
    html.block()
    return html
}

/**
 * 扩展函数
 */
fun BlockNode.head(block: BlockNode.() -> Unit): BlockNode {
    val head = BlockNode("head")
    head.block()
    this.children += head
    return head
}

/**
 * 扩展函数
 */
fun BlockNode.body(block: BlockNode.() -> Unit): BlockNode {
    val body = BlockNode("body")
    body.block()
    this.children += body
    return body
}

fun main() {
    val htmlContent = html {
        head {
            //重载String的 invoke 运算符,接收一个 Lambda
            "meta"{
                //重载String 的 invoke 运算符,接收一个 Any
                "charset"("UTF-8")
            }
        }
        body {
            "div" {
                "style"(
                    """
                    width: 200px; 
                    height: 200px; 
                    line-height: 200px; 
                    background-color: #C9394A;
                    text-align: center
                    """.trimIndent()
                )
                "span" {
                    "style"(
                        """
                        color: white;
                        font-family: Microsoft YaHei
                        """.trimIndent()
                    )
                    //重载 String 的 + 运算符
                    +"Hello HTML DSL!!"
                }
            }
        }
    }.render()

    File("Kotlin.html").writeText(htmlContent)
}

​ 其实就是遍历。在 main 方法中 调用 html 方法,传入一个 lambda。在 html 方法中执行这个lambda。然后就是执行 head 方法,head 中 先执行 head 里面的方法,一直递归执行,知道执行到最后一个后才会 this.children += head。然后由内到外执行。执行完后就到了 body。也是同样的道理,递归遍历,children 集合中的数据排列顺序都是从 html 的内部一直到外部。

​ 最终调用功能 render 函数,从外向内的递归遍历出来。


  • 高阶函数

    • 概念:参数类型包含了函数,或者返回值类型为函数。函数可被传递
    • 常见的高阶函数:forEach,map
    • 高阶函数的调用
  • 内联函数

    • 内联的概念:将函数放在调用处执行,提高了性能
    • 内联函数的写法: inline
    • 高阶函数的内联
      • return,non-local-return
    • 内联属性
  • 几个有用的函数

    • 按返回值的结果
      • let,run
    • 返回 Receiver
      • also ,apply
    • 自动关闭资源
      • use
  • 集合变换与序列

    • 集合映射

      • filter,map,flatMap
    • 集合聚合

      • fold ,reduce,sum,zip
    • 懒序列的机制

  • SAM

    • 匿名内部类
    • SAM 概念:一个抽象方法
  • 高阶函数

    • 概念:参数类型包含了函数,或者返回值类型为函数。函数可被传递
    • 常见的高阶函数:forEach,map
    • 高阶函数的调用
  • 内联函数

    • 内联的概念:将函数放在调用处执行,提高了性能
    • 内联函数的写法: inline
    • 高阶函数的内联
      • return,non-local-return
    • 内联属性
  • 几个有用的函数

    • 按返回值的结果
      • let,run
    • 返回 Receiver
      • also ,apply
    • 自动关闭资源
      • use
  • 集合变换与序列

    • 集合映射

      • filter,map,flatMap
    • 集合聚合

      • fold ,reduce,sum,zip
    • 懒序列的机制

  • SAM

    • 匿名内部类
    • SAM 概念:一个抽象方法
    • Lambda:JAVA 中本质上就是 SAM 。Kt 中的 Lambda 就是匿名函数

参考自慕课网 Kotlin 从入门到精通

原创文章 119 获赞 77 访问量 3万+

猜你喜欢

转载自blog.csdn.net/baidu_40389775/article/details/105373612