首页 > 解决方案 > Kotlin 函数参数:如何定义一个可以有尾随 lambda 或接口作为参数的函数?

问题描述

我发现了两个类似的代码:

binding.playButton.setOnClickListener (
    Navigation.createNavigateOnClickListener(R.id.action_titleFragment_to_gameFragment)
)
binding.playButton.setOnClickListener {
    Navigation.findNavController(it).navigate(R.id.action_titleFragment_to_gameFragment)
}

来自android视图类的Java代码:

    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

问题是:如何创建可以使用尾随 lambda 或接口作为参数的函数?我得到类型不匹配。

    interface One {
        fun a(): Int
    }

    class OneImp : One {
        override fun a(): Int {
            return 4
        }
    }

    fun test(one: One) {
        val a = one
    }

   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
       val a = OneImp()
       test (a)   //works fine
       test {
            a //error
       }
   }

错误:

Type mismatch.
Required:
TitleFragment.One
Found:
() → TitleFragment.OneImp

更新:

在@Jenea Vranceanu 的回答之后,我在测试SAM 时发现了我的错误(我使用了kotlin 文件中的接口,而所有代码都应该在java 中)。解决方案将是:(在 kotlinv v1.4 版本之前)创建一个 java 文件:

public class Mine {
    public interface One {
        int a();
    }

    public class OneImpl implements One {
        @Override
        public int a() {
            return 4;
        }
    }

    public void test(One one) {}
}

然后我可以同时使用函数参数和 lambda。现在在 kotlin 文件中:

 Mine().test {4}
 val b = Mine().OneImpl()
 Mine().test (b)

PS。如果他将其添加到他的答案中,我将从此处删除。

标签: javaandroidkotlin

解决方案


您误解了binding.playButton.setOnClickListener每种情况下的工作方式。

在第一个中,Navigation组件创建View.OnClickListener传递 setOnClickListener(注意括号或圆括号):

binding.playButton.setOnClickListener (
    Navigation.createNavigateOnClickListener(R.id.action_titleFragment_to_gameFragment)
)

在 Java 中,它看起来几乎相同:

binding.getPlayButton().setOnClickListener (
    Navigation.createNavigateOnClickListener(R.id.action_titleFragment_to_gameFragment)
)

第二个示例的工作方式略有不同。首先,您View.OnClickListener使用大括号创建并定义此View.OnClickListener方法的主体onClick(View view)

binding.playButton.setOnClickListener { /* empty body of onClick(View view) */ }

您参考了大括号内的单击视图:

binding.playButton.setOnClickListener { 
    it.context // `it` is the clicked view
}

// It is the same as
binding.playButton.setOnClickListener { view ->
    view.context
}

为什么它不能完全工作?

这种定义匿名类的类型只有在 Java 中定义的 SAM 类才有可能,并且View.OnClickListener是在 Java 中定义的 SAM 类。Kotlin SAM 类/接口尚不支持此功能。

通过写作:

val a = OneImp()
test {
    a //error
}

您假设test函数声明如下所示:

fun test(one: () -> One) {
    val a = one()
}

请注意,在第二个示例中,您的问题顶部Navigation.findNavController(it).navigate没有创建View.OnClickListener对象。大括号创建了这个对象,由于这个接口只有一个抽象方法,所以没有必要编写这个方法签名,所以大括号内声明的所有内容都直接进入void onClick(View view)方法。

更新(官方文档)

Kotlin 中的 SAM 转换。这是官方文档,底部写着:

...请注意,此功能仅适用于 Java 互操作;由于 Kotlin 具有适当的函数类型,因此不需要将函数自动转换为 Kotlin 接口的实现,因此不受支持。

所以 Kotlin 接口永远不会支持它,因为不需要它。看来我对“永远不会支持SAM”是错误的。它将是,但它尚不可用。Kotlin 类的 SAM 转换将从 Kotlin 1.4 开始提供,该版本现在是一个候选版本。

更新解决方案

最初没有添加替代解决方案。发问者礼貌地要求添加下一个代码以使答案完整。谢谢!

public class Mine { 
    public interface One { 
        int a(); 
    } 

    public class OneImpl implements One { 
        @Override public int a() { return 4; } 
    } 
    
    public void test(One one) {} 
}

推荐阅读