什么是SAM 转换呢?
其实Java8中的Lambda 就是SAM转换(
Single Abstract Method
) ,因为Java中的lambda 其实不是函数类型,而是需要一个 target接收,这个target就是必须是接口并且只有一个方法的 简称函数式接口
,在将lambda传递给 接口类型的时候 系统会做SAM转换
Java中 SAM转换
先来看看在Java中的 SAM转换 , 其实Java中 函数不是一等公民, Java中的lambda 是通过SAM转换实现的, 就是把lambda 表达式默认帮你转化成 实现了某个接口的匿名内部类,如下:
Java中SAM 转换
ExecutorService executro = new ThreadPoolExecutor(10,20,60,TimeUnit.SECONDS,new ArrayBlockingQueue(1));
//正常submit 方法接收的是 Runnable 接口 而我们需要传一个 lambda ,而java中lambda 是没有类型的并不是真正的函数类型,那么java在这方面就通过SAM 转换,将lambda 传成了 对应的接口,其实就是帮我们去创建了这个 匿名内部类实现了这个Runnable接口
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("Run in executor by asm");
}
});
//SAM 转化了
executorService.submit(() -> System.out.println("Run in executor by asm"));
2.Kotlin 中的SAM转换
同样 kotlin中也有SAM转换 , 但是有很多种情况分类 所以需要特别注意一下
以下都讨论 kotlin中调用的情况
2.1 Java接口 Java方法
这种是 kotlin 自古以来就支持的 也是应该的
实现方式也是 创建了匿名内部类
val executorService = ThreadPoolExecutor(10, 20, 60, TimeUnit.SECONDS, ArrayBlockingQueue(1))
//kotlin 支持sam转化,将kotlin中的 lambda(其实是一个函数的类型) 转换成 java中的接口类型
//这是kotlin 自古以来就支持的
executorService.submit{ println("run in executor by kotlin sam")}
executorService.submit({ println(" run in executor by kotlin sam ")})
2.2 Java接口 Kotlin方法
这种是kotlin1.4 之后稳定版本默认支持了
, 1.3 要额外添加配置
//kotlin中定义了一个 方法,方法参数是一个Java接口
fun useJavaInterface(runnable: Runnable){
runnable.run()
}
//java的接口 kotlin 使用 它要看版本 1.4 之后稳定了 支持 sam了
//把kotlin中的lambda 通过sam转换成了 java的 匿名内部类接口
useJavaInterface{
println("run..")
}
1.3版本需要添加gradle支持
compileKotlin {
kotlinOptions {
freeCompilerArgs = ["-XXLanguage:+NewInference", "-XXLanguage:+SamConversionForKotlinFunctions"]
}
}
2.3 Kotlin接口 Kotlin方法
这种是广为人知的菜鸡Kotlin 不支持的sam转换情况,但是现在可以通过接口前添加 fun 实现了
//定义一个 kotlin的接口
interface KotlinRunnable{
fun run()
}
//定义了一个方法 它的入参是 上面定义的 kotlin 接口
fun submitKotlinRunnable(kotlinRunnable: KotlinRunnable){
kotlinRunnable.run()
}
//直接调用接口 传入 lambda 是不支持的!!!
submitKotlinRunnable{
}
可以看出需要的是 ()->Unit 函数类型 而不是这个 KotlinRunnable接口类型
接口前面添加fun 实现sam 转换
//kotlin的接口前面添加 fun !!!
fun interface KotlinRunnable{
fun run()
}
2.4 Kotlin 接口 Java使用
注意还是 kotlin中调用这个Java方法
//定义一个 kotlin的接口
interface KotlinRunnable{
fun run()
}
public class TestSam {
//定义一个 java 方法 使用上面定义的 kotlin接口
public void useKotlinRunnable(KotlinRunnable kotlinRunnable){
kotlinRunnable.run();
}
}
kotlin中使用 上面的java方法
val testSam = TestSam()
testSam.useKotlinRunnable{
}
可以看出 和第三种情况差不多 也是无法使用的
接口前面添加fun 实现sam 转换
//添加了 fun
fun interface KotlinRunnable{
fun run()
}
此时可以看到 也可以直接使用 lambda了
注意点
总结:kotlin中要使用 SAM 转换的话,需要 这个接口是 Java接口
,如上面的 Runnable接口 ,而kotlin接口是不能的
兼容Java的 很多Android 库中 比如View.setOnClickListener(listneer)
2.5 抽象类都不支持!
java中的就不演示了 , 看看 kotlin中的 抽象类
//在kotlin中定义一个 抽象类
abstract class KotlinAbstractRunnable{
abstract fun run()
}
//再定义一个方法 入参为上面定义的 抽象类
fun submitKotlinAbstractRunnable(kotlinAbstractRunnable: KotlinAbstractRunnable){
kotlinAbstractRunnable.run()
}
可以看到这样直接传入 lambda 是不可以的 不能sam转化成 抽象类, 而且抽血类前面也不能像接口一样添加fun
//要想使用,只能传入匿名实现抽象接口的类
submitKotlinAbstractRunnable(object: KotlinAbstractRunnable() {
override fun run() {
TODO("Not yet implemented")
}
})
2.6 为什么 Kotlin中的SAM 有限制
为什么Kotlin中SAM 有这么多限制呢:
那是因为 Kotlin中 Lambda是有类型的 是函数类型, 所以建议我们直接使用函数类型去传递 而不是接口传递
有人会说 啊, kotlin中它定义的 函数类型很丑不好看, 其实也可以通过 typealias
来给函数类型一个别名如下:
函数类型的 别名
因为函数的类型() -> Unit 写起来 不是很好看 可以定义一个别名
typealias FunctionNoParamNoUnit = () ->Unit
//别名定义
typealias FunctionNoParamNoUnit = () ->Unit
fun normalSubmit(lambda : () -> Unit){
lambda()
}
fun typeAliasParamMethod(lambda: FunctionNoParamNoUnit){
lambda()
}
3. Kotlin 的SAM 坑
/**
* SAM 转换的 坑 对于Android 开发经常会使用 各种Listener 要注意创建 Listener的时候 不要通过kotlin的lambda去创建,因为这样的话得到的是 kotlin的函数类型 如 () -> Unit
* 那么再传给 Java的接口类型的时候 会自动进行SAM转换,导致 add 和 remove listener 不是同一个对象
*/
fun main(args: Array<String>) {
val eventManager = EventManager();
eventManager.addOnEventListener {event->
println("onEvent $event")
}
//也就是说 上面的lambda 形式会被 进行SAM转换成下面这样 , 此时创建的 是一个匿名方式创建的
eventManager.addOnEventListener(object:EventManager.OnEventListener{
override fun onEvent(event: Int) {
println("johnny $event")
}
})
//此时你去remove ,这个remove 的是哪个呢? ,如下面这样 它还是会通过匿名类部类方式创建
eventManager.removeOnEventListener {
}
//上面等同于这个
eventManager.removeOnEventListener(object :EventManager.OnEventListener{
override fun onEvent(event: Int) {
}
})
//这时候该怎么办呢? 这样行吗? 也不行
val onEvent = {event:Int ->
println("OnEvent $event")
}
//可是这里还是 函数类型啊 onEvent (Int)->Unit ,它还是会被SAM转换成 匿名内部类,并且是一个新的对象
eventManager.addOnEventListener(onEvent)
//那么这个remove 也remove 不掉的!
eventManager.removeOnEventListener(onEvent)
//val eventListener = EventManager.OnEventListener { }
//那该怎么办呢?
//其实创建的时候 不要直接通过kotlin的 lambda 去创建 ,通过下面这种方式创建 得到的类型是 EventManager.OnEventListener 类型
val onEvent2 = EventManager.OnEventListener({event: Int -> println("OnEvent $event") })
val onEvent3 = EventManager.OnEventListener{
event: Int ->
println("OnEvent $event")
}
//通过 匿名内部类去实现 也行 和上面一样
val onEvent4 = object: EventManager.OnEventListener{
override fun onEvent(event: Int) {
println("OnEvent $event")
}
}
//这个就和上面一样,上面的object: EventManager.OnEventListener 就是实现一个 OnEventListener接口方式
class testClass : EventManager.OnEventListener{
override fun onEvent(event: Int) {
println("")
}
}
val eventNi = testClass()
eventManager.addOnEventListener(eventNi)
//此时 add remove都是同一个对象,不会经过SAM转换了
eventManager.addOnEventListener(onEvent2)
eventManager.removeOnEventListener(onEvent2)
eventManager.addOnEventListener(onEvent4)
}
总结
本篇主要介绍了 Kotlin中 SAM转换问题, 先从Java的SAM转换引入, 在Java中它的Lambda 就是通过SAM转换成匿名内部类接口的, 而Kotlin中有好几个类型,如下:
-
Java 的接口 Java方法引用 : Kotlin 默认就支持的
-
Java 的接口 Kotlin方法引用: Kotlin 1.4 之后稳定支持, 1.3 需要使用额外配置
-
Kotlin的 接口 Kotlin方法引用: 以前是不支持的 1.4 后
在接口前面添加fun
即可支持sam转换 -
Kotlin 的接口Java方法引用: 和上面一样 也需要添加 fun 才能支持
-
抽象类 : 不支持
1.Kotlin定义的接口 前面添加fun 才能支持 sam 转换 , 2.Java的接口 Kotlin引用则 需要1.4以及以上的版本才行 3.Java接口 Java引用 自古以来都支持
原文始发于微信公众号(Johnny屋):Kotlin 和 Java 中 SAM 转换
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/89773.html