kotlin函数和Lambda表达式——>内联函数
1.内联函数
使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,并且会捕获一个闭包。即那些 在函数体内会访问到的变量。内存分配(对于函数对象和类)和虚拟调用会引入运行时间开销。
但是在许多情况下通过内联化 lambda 表达式可以消除这类的开销。下述函数是这种情况的很好的例 子。即 lock() 函数可以很容易地在调用处内联。考虑下面的情况
lock(l) { foo() }
编译器没有为参数创建一个函数对象并生成一个调用。取而代之,编译器可以生成以下代码:
l.lock()
try {
foo()
} finally {
l.unlock()
}
为了让编译器这么做,我们需要使用 inline 修饰符标记 lock() 函数:
inline fun <T> lock(lock: Lock, body: () -> T): T { ...... }
inline 修饰符影响函数本身和传给它的 lambda 表达式:所有这些都将内联到调用处。
内联可能导致生成的代码增加;不过如果我们使用得当(即避免内联过大函数),性能上会有所提升,尤 其是在循环中的“超多态(megamorphic)”调用处。
2.禁用内联
如果希望只内联一部分传给内联函数的 lambda 表达式参数,那么可以用 noinline 修饰符标记不希望内联的函数参数
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { ...... }
可以内联的 lambda 表达式只能在内联函数内部调用或者作为可内联的参数传递,但是 noinline的可以以任何我们喜欢的方式操作:存储在字段中、传送它等等。
需要注意的是,如果一个内联函数没有可内联的函数参数并且没有具体化的类型参数,编译器会产生一 个警告,因为内联这样的函数很可能并无益处(如果你确认需要内联,则可以用@Suppress("NOTHING_TO_INLINE") 注解关掉该警告)。
非局部返回
在 Kotlin 中,我们只能对具名或匿名函数使用正常的、非限定的 return 来退出。这意味着要退出一个 lambda表达式,我们必须使用一个标签,并且在lambda表达式内部禁止使用裸 return,因为lambda 表达式不能使包含它的函数返回:
fun foo() {
ordinaryFunction {
return // 错误:不能使 `foo` 在此处返回
}
}
但是如果 lambda 表达式传给的函数是内联的,该 return 也可以内联,所以它是允许的: inline fun inlined(block: () -> Unit) { println("hi!") }
fun foo() {
inlined {
return // OK:该 lambda 表达式是内联的
}
}
}
这种返回(位于 lambda 表达式中,但退出包含它的函数)称为非局部返回。我们习惯了在循环中用这种结构,其内联函数通常包含
fun hasZeros(ints: List<Int>): Boolean {
ints.forEach {
if (it == 0) return true // 从 hasZeros 返回
}
return false
}
请注意,一些内联函数可能调用传给它们的不是直接来自函数体、而是来自另一个执行上下文的 lambda 表达式参数,例如来自局部对象或嵌套函数。在这种情况下,该 lambda 表达式中也不允许非局 部控制流。为了标识这种情况,该 lambda 表达式参数需要用 crossinline 修饰符标记:
inline fun f(crossinline body: () -> Unit) {
val f = object : Runnable {
override fun run() = body()
}
// ......
}
break和continue在内联的 lambda 表达式中还不可用,但我们也计划支持它们。
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<MyTreeNode>()
为能够这么做,内联函数支持具体化的类型参数,于是我们可以这样写
inline fun <reified T> TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
p = p.parent
}
return p as T?
}
我们使用 reified 修饰符来限定类型参数,现在可以在函数内部访问它了,几乎就像是一个普通的类 一样。由于函数是内联的,不需要反射,正常的操作符如 !is 和 as 现在都能用了。此外,我们还可以按 照上面提到的方式调用它:myTree.findParentOfType<MyTreeNodeType>() 。
虽然在许多情况下可能不需要反射,但我们仍然可以对一个具体化的类型参数使用它:
inline fun <reified T> membersOf() = T::class.members
fun main(s: Array<String>) {
println(membersOf<StringBuilder>().joinToString("\n"))
}
普通的函数(未标记为内联函数的)不能有具体化参数。不具有运行时表示的类型(例如非具体化的类型 参数或者类似于 Nothing 的虚构类型)不能用作具体化的类型参数的实参。
4.内联属性(自1.1起)
inline 修饰符可用于没有幕后字段的属性的访问器。你可以标注独立的属性访问器:
val foo: Foo
inline get() = Foo()
var bar: Bar
get() = ......
inline set(v) { ...... }
你也可以标注整个属性,将它的两个访问器都标记为内联
inline var bar: Bar
get() = ......
set(v) { ...... }
在调用处,内联访问器如同内联函数一样内联
5.公有API内联函数的限制
当一个内联函数是 public 或 protected 而不是 private 或 internal 声明的一部分时,就会 认为它是一个模块级的公有 API。可以在其他模块中调用它,并且也可以在调用处内联这样的调用。
这带来了一些由模块做这样变更时导致的二进制兼容的⻛险⸺声明一个内联函数但调用它的模块在 它修改后并没有重新编译。
为了消除这种由非公有 API 变更引入的不兼容的⻛险,公有 API 内联函数体内不允许使用非公有声明, 即,不允许使用 private 与 internal 声明以及其部件。
一个 internal 声明可以由 @PublishedApi 标注,这会允许它在公有 API 内联函数中使用。当一 个 internal 内联函数标记有 @PublishedApi 时,也会像公有函数一样检测其函数体。
kotlin函数和Lambda表达式——>内联函数的更多相关文章
- c++中函数的参数传递,内联函数和默认实参的理解
1.参数传递 1)函数调用时,c++中有三种传递方法:值传递.指针传递.引用传递. 给函数传递参数,遵循变量初始化规则.非引用类型的形参一相应的实参的副本初始化.对(非引用)形参的任何修改仅作用域局部 ...
- 转-C++之虚函数不能定义成内联函数的原因
转自:https://blog.csdn.net/flydreamforever/article/details/61429140 在C++中,inline关键字和virtual关键字分别用来定义c+ ...
- Python函数与lambda 表达式(匿名函数)
Python函数 一.函数的作用 函数是组织好的,可重复使用的,用来实现单一或相关联功能的代码段 函数能提高应用的模块性和代码的重复利用率 python 内置函数:https://docs.pytho ...
- 函数新特性、内联函数、const详解
一.函数回顾与后置返回类型 函数定义中,形参如果在函数体内用不到的话,则可以不给形参变量名字,只给其类型. 函数声明时,可以只有形参类型,没有形参名 把函数返回类型放到函数名字之前,这种写法,叫前置返 ...
- 堆(stack) 之 c 和 c++模板实现(空类默认成员函数 初谈引用 内联函数)
//stack 的基本操作 #include <iostream> using namespace std; const int maxn = 3; typedef struct Stac ...
- 预处理、const、static与sizeof-为什么不把所有的函数都定义成内联函数
1:内联是以代码膨胀(复制)为代价的,仅仅省去了函数调用的开销,从而提高函数的执行效率.如果执行函数体内代码的时间相比于函数调用的开销较大,那么效率的收获会很小.另一方面,每一处内联函数的调用都要复制 ...
- 第三天 函数 三元运算 lambda表达式 内置函数 文件操作
面向过程: 直接一行一行写代码,遇到重复的内容复制黏贴. 不利于代码阅读 代码没有复用 面向对象 将代码块定义为函数,以后直接调用函数 增强了复用性 函数的定义方法 def 函数名(传递参数): 函数 ...
- 特殊用途语言特性——默认参数、内联函数和constexptr函数
1 默认实参 某些函数有这样一些参数,在函数的很多次调用中它们都被赋予一个相同的值,此时,我们把这个反复出现的值称为函数的默认实参.调用含有默认实参的函数时,可以包含该实参,也可以省略该实参. 我们可 ...
- C++内联函数与宏定义
用内联取代宏: 1.内联可调试: 2.可进行类型安全检查或自动类型转换: 3.可访问成员变量. 另外,定义在类声明中的成员函数自动转化为内联函数. 文章(一) 内联函数与宏定义 在C中,常用预处理语句 ...
- 内联函数 inline 漫谈
内联函数存在的结论是: 引入内联函数是为了解决函数调用效率的问题 由于函数之间的调用,会从一个内存地址调到另外一个内存地址,当函数调用完毕之后还会返回原来函数执行的地址.函数调用会有一定的时间开销,引 ...
随机推荐
- 【FastDFS】06 SpringBoot实现上传
创建SpringBoot工程: 再导入所需要的依赖: <dependency> <groupId>net.oschina.zcx7878</groupId> < ...
- springboot中统一日志输出logback
1.背景 为了便于分析和记录系统的运行,一个系统输出其运行的关键日志是非常必要的 比如输出:请求参数.请求url.请求方式.执行的sql.重要操作的日志.响应结果等 而这些日志中,大部分不需要我们手动 ...
- php 开发日常收获
最近项目需求: 仿制某网站的菜单功能 效果如下: 效果大概就是这样啦.分析下吧:主要是分级查询我的思路:首先从数据库中查询所有的顶级目录: 目前就是这几个顶级目录在数据库中特点就是 父级id是0(一般 ...
- 洛谷P1226 【模板】快速幂
1.快速幂模板 前置知识 一个数字n,它的二进制位数一定是log2n向下取整+1: 快速幂模板代码 这段代码实现了快速幂算法(Exponentiation by squaring),用来计算 ( an ...
- CF506D题解
Mr. Kitayuta's Colorful Graph 算法:根号分治. 题目大意先说一下:给一个 \(n\) 点 \(m\) 边的无向图,边有颜色.\(q\) 组询问,每次给出 \(u,v\), ...
- java本地增量打包工具
在打增量包每次都需要将class文件.jsp文件等拷贝到增量包中比较麻烦.所以就写了一个增量打包工具. 工作原理:根据文件的最后修改时间来打增量. 1.查找Java类增量:根据eclipse工程下的. ...
- 【CMake系列】05-静态库与动态库编译
在各种项目类型中,可能我们的项目就是一个 库 项目,向其他人提供 我们开发好的 库 (windows下的 dll /lib : linux下的 .a / .so):有时候在一个项目中,我们对部分功能 ...
- 兼容ios11的正则匹配
ios11不支持正则零宽断言,以字符串 $哈哈哈(sh039488)$ 为例: 不兼容写法:/\$(?<=\$).*?(?=\)\$)\)\$/g 兼容写法:/\$(.*?)\)\$/g
- (零) React Native 项目开发拾遗
一位离职的前端同事,最近接了个 React Native 的活儿,遇到许多搞不定的问题,于是找到我帮忙"补课"(没有系统的学习 React Native,也不具备原生 Androi ...
- Linux下载安装jdk1.8
Linux下载安装jdk1.8 一.下载 wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=a ...