Kotlin 和 Java 中 SAM 转换

什么是SAM 转换呢?

其实Java8中的Lambda 就是SAM转换(Single Abstract Method) ,因为Java中的lambda 其实不是函数类型,而是需要一个 target接收,这个target就是必须是接口并且只有一个方法的 简称函数式接口,在将lambda传递给 接口类型的时候 系统会做SAM转换

Kotlin 和 Java 中 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接口类型

Kotlin 和 Java 中 SAM 转换


接口前面添加fun 实现sam 转换

//kotlin的接口前面添加 fun !!!
fun interface KotlinRunnable{
   fun run()
}


Kotlin 和 Java 中 SAM 转换



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{

  }

可以看出 和第三种情况差不多 也是无法使用的

Kotlin 和 Java 中 SAM 转换


接口前面添加fun 实现sam 转换

//添加了 fun
fun interface KotlinRunnable{
   fun run()
}

此时可以看到 也可以直接使用 lambda了

Kotlin 和 Java 中 SAM 转换


注意点

总结: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

Kotlin 和 Java 中 SAM 转换


//要想使用,只能传入匿名实现抽象接口的类
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

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!