Kotlin 学习笔记(十) 内联函数

853 阅读3分钟

前言 本博客为本人学习笔记,如有不对的地方,劳请在评论区指出,望海涵

Kotlin 学习笔记 (九)

参考文章白话Kotlin

1. 内联函数

说起内联函数 我们首先要谈论起 C++

内联函数是C++的增强特性之一,用来降低程序的运行时间。当内联函数收到编译器的指示时,即可发生内联:编译器将使用函数的定义体来替代函数调用语句,这种替代行为发生在编译阶段而非程序运行阶段

1.1 内联含义

  1. 在kotlin中,函数就是对象,当你调用某个函数的时候,就会创建相关的对象。 你看,这就是空间上的开销!

  2. 当你去调用某个函数的时候,虚拟机会去找到你调用函数的位置。然后执行函数,然后再回到你调用的初始位置。 你看,这就是时间上的开销!

现在,我直接把调用的函数里面的代码放到我调用的地方,省去寻找调用函数的位置的时间。

  正常的调用函数的方式 及 开销


fun main(args: Array<String>) {

	println("你好!")
	
	//函数调用就会去寻找该函数的位置,调用完后在回到这里
	// 这个过程就是一种消耗
	shopImpl()
	
	
	fun shopImpl(): Unit {
            println("hello world1")
            println("hello world2")
        }
}

  将fun shopImpl()函数标识为内联inline fun shopImpl(),神奇的事情发生了,你的代码会变成如下的模样:

fun main(args: Array<String>) {

	println("你好!")
	
	// 我们继续调用 shopImpl函数
	shopImpl()
	
	// 但其实运行后代码 不再是 寻找shopImpl函数,
	// shopImpl 函数内容直接就在这里 不用寻找调用
	// 在编译器 编译器就已经帮我们做好了这部分的替换
	
	println("hello world1")
        println("hello world2")
	...
	
	inline fun shopImpl(): Unit {
        println("hello world1")
        println("hello world2")
    }
}

  我们最后通过 两个图片看下 普通调用方式 和 内联函数调用 编译后的区别

普通调用方式

普通调用方式

内联函数调用截图 (inline)

内联函数调用

1.2 内联函数的定义 和调用

  通过 1.1 我们已经知道 ,内联函数我们只需要在函数的头部添加 inline 关键字 就可以了

问题

  在kotlin中,函数就是对象,我们可以将函数作为参数传递给函数。   所以问题来了,若我们的内联函数中的参数有函数呢?那这个函数是不是内联函数呢?

答案

  当函数为 内联函数,其 参数如果 也是 函数 或者 lambda 函数,默认参数中的函数 默认也为内联函数


// shopImpl 函数为内联函数
// lambda 函数 printString 默认也为内联函数

inline fun shopImpl(printString:(a:String) -> Unit): Unit {
    println("hello world 1")
    println("hello world 2")
}


// 当然也可以设置为不是内联函数  在lambda 前添加 noinline 修饰符

inline fun shopImpl(noinline printString:(a:String) -> Unit): Unit {
    println("hello world 1")
    println("hello world 2")
}

2. 内联函数 和 return

  在kotlin中,return 只可以用在有名字的函数,或者匿名函数中,使得该函数执行完毕。而针对lambda表达式,你不能直接使用return

/**
 * 我们使用 return + @方法名标签 结束当前lambda函数
 */
fun main(args: Array<String>) {
    println("start")
    forItem({if(it==1)return@forItem else println(it)})
    println("end")
}

fun forItem(item: (i:Int) -> Unit) {
    var i = 0
    while (i++<5){
        item(i)
    }
}

运行结果

start  // 根据打印结果,我们看到 1 被return 了
2
3
4
5
end

但是,如果内联函数 和 return 结合使用 确实没有问题


// forItem 中可以直接使用 return,不需要标签

fun main(args: Array<String>) {
    println("start")
    forItem({if(it==1)return else println(it)})
    println("end")
}

// 设置为内联函数
//
inline fun forItem(item: (i:Int) -> Unit) {
    var i = 0
    while (i++<5){
        item(i)
    }
}

运行结果

start

  从打印结果分析,和 高级函数结合 return + label 结果差距很大,其实 理解内联函数就很好理解了,内联函数在编译期间,lambda表达式就是一段代码而已,这时候你在lambda中的return,相当于在你调用的方法内return。所以例子中的 return 会直接结束 main 函数,到时后边的 end 也没有打印。

2.2 内联属性 (V1.1起)

  对属性来说,我们会有get,set的方法来操作这个属性。get,set就是个函数,我们可以标识他们为内联函数:


val foo: Foo
    inline get() = Foo()

var bar: Bar
    get() = ...
    inline set(v) { ... }

或者 (getter setter 都为内联)


inline var bar: Bar
    get() = ...
    set(v) { ... }

2.3 具体化的类型参数

有时候我们需要访问一个作为参数传给我们的一个类型:


fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
    var p = parent
    while (p != null && !clazz.isInstance(p)) {
        p = p.parent
    }
    @Suppress("UNCHECKED_CAST")
    return p as T?
}

我们是这样使用它的:

treeNode.findParentOfType(MyTreeNode::class.java)

但是这么写不优雅,我们期望这么写:

treeNode.findParentOfType()

那么该怎么写? 内联函数支持 具体的类型参数的声明 reified 这么写的:

inline fun <reified T> TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p !is T) {
        p = p.parent
    }
    return p as T?
}