Kotlin中实现多个同签名函数式接口的解决方案探讨
问题重现
当多个函数式接口的类型签名不同时,类可以正常实现这些接口:
typealias A = () -> Unit typealias B = (something: String) -> Unit class Test : A, B { override fun invoke() { TODO("Not yet implemented") } override fun invoke(something: String) { TODO("Not yet implemented") } }
但如果添加签名相同的第三个函数式接口,编译器会抛出Conflicting Overloads或A supertype appears twice错误:
typealias A = () -> Unit typealias B = (something: String) -> Unit typealias C = (somethingElse: String) -> Unit class Test : A, B, C { override fun invoke() { TODO("Not yet implemented") } override fun invoke(something: String) { TODO("Not yet implemented") } override fun invoke(somethingElse: String) { TODO("Not yet implemented") } }
临时取巧方案(不推荐)
添加无意义的参数可以暂时绕过编译错误,但这属于投机取巧,后续新增同签名接口时仍会重复出现问题:
typealias C = (somethingElse: String, garbage: Unit?) -> Unit
值类尝试的局限性
尝试用值类区分参数类型时,由于值类会被JVM编译时消除,会出现Platform declaration clash: The following declarations have the same JVM signature (invoke(Ljava/lang/Object;)Ljava/lang/Object;)错误:
@JvmInline value class BString(val value: String) @JvmInline value class CString(val value: String) typealias A = () -> Unit typealias B = (something: BString) -> Unit typealias C = (somethingElse: CString) -> Unit class Test : A, B, C { override fun invoke() { TODO("Not yet implemented") } override fun invoke(something: BString) { TODO("Not yet implemented") } override fun invoke(somethingElse: CString) { TODO("Not yet implemented") } }
注:Kotlin KEEP 302(Binary Signature Name)提案预计会在未来解决此类JVM签名冲突问题,但当前需要其他解决方案。
正确解决方案:值类+显式定义接口
放弃使用类型别名,改为显式定义函数式接口,配合值类区分参数类型,即可正常实现多个同签名逻辑的接口:
@JvmInline value class BString(val value: String) @JvmInline value class CString(val value: String) interface A { operator fun invoke() } interface B { operator fun invoke(something: BString) } interface C { operator fun invoke(somethingElse: CString) } class Test : A, B, C { override operator fun invoke() { println("invoke A") } override operator fun invoke(something: BString) { println("invoke B - something = $something") } override operator fun invoke(somethingElse: CString) { println("invoke C - somethingElse = $somethingElse") } } fun main(args: Array<String>) { val handlerA = A::invoke val handlerB = B::invoke val handlerC = C::invoke val t = Test() handlerA(t) handlerB(t, BString("hello B")) handlerC(t, CString("hello C")) }
运行后输出:
invoke A
invoke B - something = BString(value=hello B)
invoke C - somethingElse = CString(value=hello C)
实际场景适配
比如需要让类同时实现Clickable和DoubleClickable(二者签名均为(Event) -> Unit),可以分别定义ClickEvent和DoubleClickEvent值类,再对应定义两个显式接口,即可实现无冲突的多接口实现。
内容的提问来源于stack exchange,提问作者Jan Vladimir Mostert

