惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LINUX DO - 最新话题
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
PCI Perspectives
PCI Perspectives
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
H
Heimdal Security Blog
S
Security @ Cisco Blogs
N
News | PayPal Newsroom
J
Java Code Geeks
罗磊的独立博客
Security Archives - TechRepublic
Security Archives - TechRepublic
N
News and Events Feed by Topic
V
V2EX
WordPress大学
WordPress大学
Google Online Security Blog
Google Online Security Blog
N
News and Events Feed by Topic
www.infosecurity-magazine.com
www.infosecurity-magazine.com
月光博客
月光博客
AI
AI
小众软件
小众软件
The GitHub Blog
The GitHub Blog
MongoDB | Blog
MongoDB | Blog
A
Arctic Wolf
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
美团技术团队
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Tailwind CSS Blog
S
Schneier on Security
博客园 - 三生石上(FineUI控件)
F
Full Disclosure
B
Blog RSS Feed
Forbes - Security
Forbes - Security
S
SegmentFault 最新的问题
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
人人都是产品经理
人人都是产品经理
云风的 BLOG
云风的 BLOG
Jina AI
Jina AI
Cisco Talos Blog
Cisco Talos Blog
U
Unit 42
Project Zero
Project Zero
H
Hacker News: Front Page
Y
Y Combinator Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
The Cloudflare Blog
大猫的无限游戏
大猫的无限游戏
S
Secure Thoughts
The Hacker News
The Hacker News
Microsoft Azure Blog
Microsoft Azure Blog

音视频开发进阶

音视频教程-第三节 音视频教程-第二节 真的,AI 可能就是新时代的信息差 充值 Cursor 之后,工作有了哪些变化?🤔 个人'蒸馏'大模型能做哪些有意思的事情 DeepSeek 大模型在 Mac 上的部署和运行 音视频教程-第一节 【WebRTC 专栏】-- Android 开发集成 WebRTC 库的几种方式 【WebRTC 专栏】-- 在 Mac M1 等系列芯片编译和开发 WebRTC-Android 库 Meta Llama3 大模型在 Mac 上的部署和运行 iOS VideoToolBox 解码 HEVC Open-GOP 视频的问题排查 Flutter 状态管理之 InheritedWidget 使用和分析 用 ChatGPT 回答技术问题怎么样 ? 音视频开发系统入门大致路线 UE 4.27 添加自定义 ShadingModel 用 UE4 虚幻引擎做个捏脸小功能~~ UE4 材质练习 之 凹凸贴图偏移的使用 UE4 材质练习系列基础 OpenGL上下文创建以及共享机制 007 | 播放器系列专栏-解析 MP4 文件读取信息 006 | 播放器系列专栏-在 Mac 上查看 MP4 格式信息 干货 | 快速抽取缩略图是怎么练成的? 关于直播、WebRTC、FFmpeg 的那些事 005 | 播放器系列专栏-在 Windows 上查看 MP4 格式信息 将音视频中的花屏、绿屏、黑屏问题一网打尽 关于音视频里面的解码帧率和渲染帧率 004 | 播放器系列专栏-认识MP4视频(下) 003 | 播放器系列专栏-认识MP4视频(上) 入门或者转行音视频,应该要怎么做? H264视频文件如何缩放分辨率 002 | 播放器系列专栏-FFmpeg依赖库的配置 001 | 播放器系列专栏-关于播放器项目的一个小实践 Seek策略以及在有B帧情况下的处理 目前流媒体开发工程师工作内容主要是什么? 一个音视频领域专业问答的小圈子 干货收藏 || Vulkan Game Engine 视频教程 音视频春节假期内卷指南(实操) Vulkan 在 FFmpeg 中的支持 Windows 下 FFmpeg 和 LibX264 的编译和配置 Metal 开发 | 使用 C++ 进行接口调用 音视频开发工作经验分享 || 视频版 FFmpeg 调用 MediaCodec 硬解码到 Surface 上 代码吸猫 | 用 OpenGL 图像渲染的养猫计划 百倍变速--解码到底能不能丢 非参考帧 ?FFmpeg 有话说!!! 老生常谈-FFmpeg 的编译问题轻松搞定 FFmpeg 调用 Android MediaCodec 进行硬解码(附源码) 【WebRTC 专栏】--创建相机预览 Unity Shader 光照基础之 Half Lambert 光照模型 Unity Shader 光照基础之Lambert光照模型 Unity Shader 光照基础内容 Unity Shader 显示一张图片纹理 UnityShader 的基本概念 Unity 物体的基本操作 C++ 模板系列小结07-尾置返回类型 C++ 模板系列小结06-可变参数模板特性 C++ 中的多线程的使用和线程池建设 C++ 模板系列小结05-模板类型作为模板参数 C++ 模板系列小结04-类模板中的成员模板 C++ 模板系列小结03-在模板中指定变量类型 C++ 模板系列小结02-非类型模板参数 C++ 模板系列小结01-函数模板和类模板 从零打造渲染引擎系列01-什么是渲染引擎 iOS开发 - 在 Swift 中去调用 C/C++ 代码 2021 技术新番 - 从零打造渲染引擎系列 iOS 音视频开发的一些基础准备工作 音视频交流群又来啦~~~ 【WebRTC 专栏】WebRTC & Android 开发学习环境搭建~ 【喜大普奔】域名终于备案通过啦 Shader 优化 | OpenGL 绘制网格效果 【音视频连载-011】第二季 FFmpeg 一层一层获取文件信息 KodeLife | Shader 实时编辑预览的强大工具使用实践 推荐几个堪称教科书级别的 Android 音视频入门项目 【音视频连载-010】第二季 FFmpeg 日志打印 【音视频连载-008】基础学习篇-SDL 播放 PCM 音频文件(下) 【音视频连载-007】基础学习篇-SDL 播放 PCM 音频文件(上) 【音视频连载-006】基础学习篇-SDL 播放 YUV 视频文件 【音视频连载-005】基础学习篇-SDL 加载 YUV 文件并显示 【音视频连载-004】基础学习篇-SDL 加载图片并显示 【音视频连载-003】基础学习篇-SDL 消息循环和事件响应 【音视频连载-002】基础学习篇-SDL 创建窗口并显示颜色 【音视频连载-001】基础学习篇- SDL 介绍以及工程配置 LearnOpenGL 源码在 MAC 上的编译与调试 2019 年终总结与回顾 Android NDK 开发的免费技术视频来啦~~ OpenGL 实现视频编辑中的转场效果 OpenGL 实践之贝塞尔曲线绘制 图像库 libjpeg-turbo 编译与实践 图像库 libpng 编译与实践 rust 开发编译 Android 动态库实践 Android NDK 开发 —— 从 Assets 文件夹加载图片并上传纹理 简单易用的图像解码库介绍 —— stb_image 博客图床迁移记 进击的 Vulkan 移动开发之 SwapChain 进击的 Vulkan 移动开发之 Command Buffer 进击的 Vulkan 移动开发之 Instance & Device & Queue 进击的 Vulkan 移动开发(一)之今生前世 Java 显式锁 Lock 与条件队列 C++ 标准容器库小结 一文读懂 YUV 的采样与格式 《OpenGL ES 3.x 游戏开发》碰撞检测之 AABB 包围盒
Kotlin 中的 run、let、with、apply、also、takeIf、takeUnless 语法糖使用和原理分析
音视频开发进阶 · 2018-06-29 · via 音视频开发进阶

一个专注音视频领域的小圈子

在 Kotlin 有一些可以简化代码的语法糖,比如 run、let、with、apply、also、takeIf、takeUnless 等。 再不明白这些语法糖的情况下去看 Kotlin 代码就会一脸懵逼,可当明白之后就会觉得原来可以这样简化。

带接收者的函数字面值

使用这些语法糖之前回顾一下 Kotlin 的函数式编程,在分析 Kotlin 使用 Anko 构建布局 文章中有提到 带接收者的函数字面值

它的形式是这样的:

// 定义一个类
class ReceiveObject 
// 定义一个函数
fun exec(invoke: ReceiveObject.()-> Int){}

在 Kotlin 中,函数也可以当做变量传参,例如:

fun funAsArg(args:()->Int){}
// 调用
funAsArg { 2 }

args 是变量名,它的类型就是函数,函数形式在变量名后面约定:()->Int,函数没有参数,但是会返回一个 Int 类型的值。

而带接收者的函数字面值,就是在作为传入参数的函数变量的具体函数形式的参数前面多了接收者对象,简单说就是在 ()前面多了一个点和一个对象,成了如下的形式:

fun exec(invoke: ReceiveObject.()-> Int){}

就是这多了的一个点和一个对象,让它有了不一样的功能。

简单的说,invoke 变量是一个函数作为变量,需要传递一个具体函数实现作为形参给 invoke,那么在具体函数实现里面就可以调用接收者对象 ReceiveObject 的相关方法,如下:

	// 接收者对象,有个 show 方法
	class ReceiveObject{
	    fun show(){
	        println("call")
	    }
	}
	// 具体函数实现
	val invoke: ReceiveObject.() -> Int = {
        this.show() // 用 this 指代 接收者对象 ReceiveObject
        2
    }
    exec(invoke)

如上,在 invoke 方法里面使用 this 指代 ReceiveObject 对象,可以调用它的方法。

而 invoke 变量是作为参数传递给 exec 函数的,如果 exec 函数为空,那么 inkoke 具体实现的 show 方法也不会被调用的,在 exec 中调用 invoke 的方法如下:

fun exec(invoke: ReceiveObject.() -> Int){
    val receObj = ReceiveObject()
    // 两种调用形式
	// 类似于 ReceiceObject 拓展函数一样的调用
    receObj.invoke() 
    // 把 ReceiceObject 作为参数传递给 invoke 调用
    invoke(receObj)
}

在 exec 的具体调用中,我们需要构造一个 ReceiveObject 对象实例,不然怎么去调用它的 show 方法呢。

在上面的例子中,还需要构造一个指定的接收者对象实例才能完成 invoke 的调用,而 Kotlin 的语法糖中还有一种叫做 拓展函数

拓展函数

拓展函数相当于给某个类添加函数,但这个函数并不属于这个类的函数,和 static 方法是两码事。

	fun Context.showToast(msg: String) {
	    Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
	}

在拓展函数中,使用 this 指代被拓展的类实例,上面代码中 this 指代就是 Context 。

有了 拓展函数和带接收者的函数字面值,就可以实现文章标题提到的那些语法糖了。

例如,针对 ReceiveObject 对象添加它的拓展函数,拓展函数的参数又是一个函数,函数是带接收者的函数字面值,这个接收者对象就是 ReceiveObject 对象它本身,这样调用 invoke 方法就不用再构造 ReceiveObject 对象了。

fun ReceiveObject.exec(invoke: ReceiveObject.() -> Int){
    invoke()
}

语法糖

下面介绍的语法糖都是位于 Kotlin Standard.kt 文件中的。

run 语法糖

run 的语法糖有两种:

public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

这种语法糖传递的参数就仅仅是一个函数,不是带接收者对象的函数字面值,它的返回结果就是 block 函数调用后的结果。

调用示例:

    var result = kotlin.run { 
            "value"
        }

相对于给 arg 变量赋值为 value 字符串。

run 的另一种语法糖:

public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

首先,这个语法糖是一个拓展函数,而且用到了泛型 <T,R>,T 类型的拓展函数,返回的是 R 类型,T 和 R 可以相同。

其次,传递的参数是带接收者对象的函数字面值,也就是说可以在 block 函数里面调用 T 的相关方法,通过 this 来指代 T ,在 run 方法内部就是调用了 block 方法,返回 block 函数调用后的结果。

调用示例:

            val result = "a".run {
                this.plus("b")
            }

Contracts DSL

在 run 的语法糖里面还出现了如下一段代码:

 contract {
      callsInPlace(block, InvocationKind.EXACTLY_ONCE)
   }

Google 了一番之后

得出原来这是 Kotlin 1.2.x 版本中出现的,但实际并没有用,是 Kotlin 后续发展用来解决如下代码问题的:

if (!x.isNullOrEmpty()) { 
  // we know that 'x != null' here 
  println(x.length)
}

假设 x 是可以为 null 的,经过 isNullOrEmpty 函数判断之后,再执行 println 函数,那么它肯定就不是 null 了,就不需要再加两个 !! 来表示 x 不为 null 了,而现在的情况是要添加 !!

从 Google 来的信息得知, contract 这段代码就是为了这样的问题的。

由于语法糖都有那样一段代码,所以就先把它们去掉了。

let 语法糖

public inline fun <T, R> T.let(block: (T) -> R): R {
    return block(this)
}

let 语法糖传递的参数是一个函数,不是带接收者的函数字面值,但 block 函数的参数就是 T 类型,所以可以在 block 里面调用 T 类型的方法,但不能通过 this 来指代 T 了,通过 it 来指代 T 类型。

调用示例:

            val result = "a".let {
                it.plus("b")
            }

with 语法糖

public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    return receiver.block()
}

with 语法糖不再是一个拓展函数了,而是需要在语法糖的第一个参数里面传入接收者对象的实例,第二个参数就是带接收者的函数字面值实例,返回的也是 block 调用的结果,这一点和 run 语法糖类似。

调用示例:

            val result = with("a") {
                this.plus("b")
            }

apply 语法糖

public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}

apply 语法糖和 run 语法糖都类似,只不过它返回的不是 block 函数调用的结果,而是返回调用者本身,返回 T 类型。

also 语法糖

public inline fun <T> T.also(block: (T) -> Unit): T {
    block(this)
    return this
}

also 语法糖和 let 语法糖有点类似,只不过返回的结果不是 block 调用结果,而是返回它本身,返回 T 类型。

调用示例:

            var result = "a".also {
                it.plus("b")
            }

takeIf 语法糖

public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    return if (predicate(this)) this else null
}

takeIf 语法糖会调用 predicate 函数进行判断,如果为 true 就返回它本身,否则返回 null 。

takeUnless 语法糖

public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
    return if (!predicate(this)) this else null
}

takeUnless 和 takeIf 正好相反,如果 predicate 返回 false 就返回它本身,否则返回 null 。

总结

这么多的语法糖,其实他们的原理都是类似的,共同点在于都是有返回值的,而区别就在于对原有的值进行了哪些操作,然后如何返回最终的值。

最后,光是了解他们的原理和调用情况还是不够的,再不影响代码阅读的情况下要把它们引入到我们的代码中去,灵活地使用它们。

原创文章,转载请注明来源:    Kotlin 中的 run、let、with、apply、also、takeIf、takeUnless 语法糖使用和原理分析