关于Kotlin中SAM转换的那些事
作者:Dexlind 发布时间:2022-02-09 15:14:38
前言
随着 Kotlin 1.4 正式发布,关于 SAM 转换的一些问题就可以盖棺定论了。因为这里要讲的都是些旧的东西,所以这是一篇灌水文。
Kotlin对SAM转换的支持情况
在 1.4 发布之前,经常有新人在群里提出关于 SAM 转换的问题。
为了说明这个问题,要分成几个情况来讨论。
我们需要区分这个接口是Java接口还是Kotlin接口:
// 这是Java
interface JavaSome {
void some();
}
// 这是Kotlin
interface KotlinSome {
fun some()
}
以及区分在Java还是Kotlin里使用该接口:
// 这是Java, ISome是一个接口
void useSome(ISome some) {}
// 这是Kotlin, ISome是一个接口
fun useSome(some: ISome) {}
两两相乘,我们就需要对4种情况进行讨论。当然,useSome 函数都是在 Kotlin 里调用。
1、Java接口,Java使用
// Java
void useSome(JavaSome some) {}
// Kotlin
useSome {} // OK
这种情况下的 SAM 转换,是自古以来 Kotlin 就支持的。
2、Java接口,Kotlin使用
// Kotlin
fun useSome(some: JavaSome) {}
useSome {} // 能否编译成功跟Kotlin版本和编译器参数有关
Kotlin 1.2 以及更旧版本不支持这种情况下的SAM转换。
Kotlin 1.3 版本,Kotlin 官方团队发现他们写的那堆类型推断算法是一座“屎山”,于是重新写了套新的类型推断算法,作为默认关闭的实验性特性加入了 1.3 版本。新的类型推断算法支持这种情况下的SAM转换,不过需要手动传入编译器参数来开启这个功能。
Kotlin 1.4 版本,由于新的类型推断算法已经默认开启,所以这种情况下可以进行SAM转换。
3、Kotlin接口,Kotlin使用
// Kotlin
fun useSome(some: KotlinSome) {}
useSome {} // 编译错误!
这就是广为人知、为人诟病的垃圾 Kotlin 不支持 SAM 转换的情况。
在 Kotlin 1.4 版本,你需要在接口前加上关键字 fun,让它成为一个 fun interface 才能享受到 SAM 转换。
// Kotlin
fun interface KotlinSome {
fun some()
}
fun useSome(some: KotlinSome) {}
useSome {} // OK
当然 1.3 版本就别想了,老老实实升级吧。
4、Kotlin接口,Java使用
// Java
void useSome(KotlinSome some) {}
// Kotlin
useSome {} // 需要是 fun interface
非常少见。
和上面的第三种情况一样,这需要 Kotlin 1.4 版本的 fun interface 才能进行 SAM 转换。
5、带有suspend函数的Kotlin接口
四天王有五个人不是常识么
fun interface Some {
suspend fun some()
}
fun useSome(some: KotlinSome) {}
useSome {} // 嘻嘻
在 Kotlin 1.4 的测试版(里程碑版、RC版),可以编译成功,但是运行起来会炸。原因在于 Kotlin 官方团队并没有写好针对这种情况的代码生成(codegen)。于是在 Kotlin 1.4 正式版,他们就 ban 掉了这样的代码,不允许 fun interface 拥有抽象 suspend 函数。
6、一些旧版本的bug
最经典的是那个安卓的LiveData的某个函数:
val liveData = MutableLiveData<Int>()
liveData.observe({ lifecycleOwner.lifecycle }, Observer { invokeMyMethod(it) })
// 第二个参数无法进行SAM转换
详见KT-14984。
新的类型推断算法修正了这个bug。
SAM Constructor
在 1.3 以及更早的版本,针对上面所说的第二种情况,可以这样使用:
// Kotlin
fun useSome(some: JavaSome) {}
useSome(JavaSome {})
想必各位过来人都知道这样的写法。
这里 JavaSome {},lambda 表达式前面的那个 JavaSome 就是所谓的 SAM 构造器(SAM constructor),或者说是 SAM 适配器(SAM adapter)。
在现在 1.4 版本里,SAM constructor 已经没什么用了,主要用途是“凭空捏出”一个 SAM 接口的实例:
val ktSome = KotlinSome {} // 需要是 fun interface
val javaSome = JavaSome {}
// 错误用法
// val ktSome: KotlinSome = {}
// val javaSome: JavaSome = {}
SAM constructor 可以理解为编译器为 SAM 接口生成了一个如下所示的辅助函数,但是实际上这个函数并不存在。
// 这是Java
interface JavaSome {
void some();
}
// 实际上并不存在的辅助函数
inline fun JavaSome(block: () -> Unit): JavaSome {
return 编译器的魔法
}
然后就有一些鲜为人知的用法,比如说这样:
// Kotlin
val lambda: () -> Unit = { println("test") }
val kepa: JavaSome = JavaSome(lambda) // 嘻嘻
kepa.some() // 输出 test
上面这段代码确实是可以跑的。
甚至是这样:
val lambda: () -> Unit = { println("test") }
val some: KFunction1<() -> Unit, JavaSome> = ::JavaSome // 嘻嘻
val kepa: JavaSome = some.invoke(lambda)
kepa.some()
这段代码 IDEA 不会提示错误,但是会编译失败。
表面上看确实有这个辅助函数,所以这样的代码可以通过 Kotlin 编译器前端的检查。但是实际上编译器的后端并没有办法针对这样的情况进行代码生成,彻底懵逼了,boom!
你学到了什么
一些无用的历史知识
关于 SAM constructor 的冷知识
本文完。
来源:https://aisia.moe/2020/09/12/kotlin-sam-history/


猜你喜欢
- 本博客可能没有那么规范,环境之类的配置。只是让你直接开始编程写python。至于各种配置网络上有多种方法。本文仅代表我的观点的一种方法。电脑
- 本文实例讲述了Python实现的最近最少使用算法。分享给大家供大家参考。具体如下:# lrucache.py -- a simple LRU
- 本篇文章博主将带大家一起学习MySQL中常用的数据查询语言。DQL(Data Query Language 数据查询语言)SELECT 语法
- Python的版本是挺折腾人的,本着简单实用的原则我介绍一下我是如何安装多版本Python的。环境:windows10(64位)Python
- 仔细观察下面两个python程序,代码一模一样,但是运行的结果却不同,就是因为最后一行return缩进的不同def powersum(pow
- JavaScript是运行在客户端的脚本,因此一般是不能够设置Session的,因为Session是运行在服务器端的。而cookie是运行在
- 中文繁体、简体的差异,在NPL中类似英文中的大小写,但又比大小写更为复杂,比如同样为繁体字,大陆、香港和台湾又不一样。先前写过一篇中文繁简转
- IE的特殊性 IE的DOM元素属性与Firefox, Opera, Safari有些不同。在IE中,我们可以给DOM添加任意自定
- 经常碰到的一个问题是limit的offset太高,如:limit 100000,20,这样系统会查询100020条,然后把前面的
- 那么,现在如果给出一个权限编号,要去检索出用后这个权限的用户集合,就会需要在逗号分隔的多个权限编号中去匹配给出的这个权限编号。如果使用lik
- 2016年9月22日凌晨,微信宣布“小程序”问世,妈的,论坛,博客全是小程序,昨天当之无愧抢了头条,当然只是开始内测了,微信公众平台对200
- TensorFlow是一个基于数据流编程(dataflow programming)的符号数学系统,被广泛应用于各类机器学习(machine
- 一、使用replace+空格ordersdetaildf['商品名称2']=ordersdetaildf['商品名称
- 如何判断一个数值(字符串)为整数不严格检查方法浮点数的自带方法is_integer()如果确定输入的内容为浮点数,是可以直接使用float数
- 1. 前言我们已经知道,对于InnoDB存储引擎而言,页是磁盘和内存交互的基本单位。哪怕你要读取一条记录,InnoDB也会将整个索引页加载到
- 本文为大家分享了mysql8.0.11客户端无法登陆的解决方法,供大家参考,具体内容如下mysql8.0.11 默认加密方式【caching
- 布局管理就是管理图形窗口中各个部件的位置和排列。图形窗口中的大量部件也需要通过布局管理,对部件进行整理分组、排列定位,才能使界面整齐有序、美
- 这篇文章主要介绍了Python autoescape标签用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值
- 索引相关1. 查询(或更新,删除,可以转换为查询)没有用到索引这是最基础的步骤,需要对sql执行explain查看执行计划中是否用到了索引,
- 感觉上次写的植物大战僵尸与俄罗斯方块的反应还不错,这次这个文章就更有动力了这次就写一个天天酷跑吧写出来的效果图就是这样了下面就更新一下全部的