Kotlin的SAM转换和踩坑
只有一个抽象方法的接口被称作函数式接口,或者单抽象方法(SAM)接口。函数式接口应用广泛,我们常见的Runnable、View.OnClickListener等都是函数式接口。

An interface with only one abstract member function is called a functional interface, or a Single Abstract Method (SAM) interface. ——kotlinlang.org

只有一个抽象方法的接口被称作函数式接口,或者单抽象方法(SAM)接口。函数式接口应用广泛,我们常见的Runnable、View.OnClickListener等都是函数式接口。

SAM转换

正常情况下,我们实现一个接口是比较麻烦的,如下

    private val redRunnable = object: Runnable {
        override fun run() {
            binding.main.setBackgroundColor(Color.RED)
        }
    }

Kotlin为我们提供了语法糖,当接口是一个SAM接口时,AS会提示我们方法可以简化:

    private val redRunnable = Runnable { binding.main.setBackgroundColor(Color.RED) }

SAM转换的踩坑

上述的例子我们做一个小Demo,两个按钮控制背景色,第一个按钮延迟2s设置红色背景,第二个按钮立即设置绿色背景。很容易写出以下的代码:

    private val redRunnable = { binding.main.setBackgroundColor(Color.RED) }

    private fun bindView() {
        binding.button1.setOnClickListener {
            handler.postDelayed(redRunnable, 2_000)
        }
        binding.button2.setOnClickListener {
            handler.removeCallbacks(redRunnable)
            binding.main.setBackgroundColor(Color.GREEN)
        }
    }

依次点击按钮1和按钮2,发现背景先变为绿色再变为红色,显然按钮2并没有成功取消掉按钮1的delay操作。你知道以上代码哪里出问题了吗?

AI的回答

AI的回复大略一看没有解决问题,这个任务显然没有执行,也没有生命周期问题。于是贴上完整代码,AI立即解决了问题。我遇到这个问题时没有贴代码给AI,苦思冥想许久才看出端倪:我这里定义的redRunnable并不是一个真正的Runnable对象,而是一个lambda(类型()->Unit)。这就导致postDelayed和removeCallbacks时SAM转换生成了不同的Runnable对象,因此取消就无法成功。

接下来看一下反编译出的java代码来验证这一猜想:

反编译1

反编译2

最后把这段代码修改正确,

最终代码


最后修改于 2024-09-24