虽然kotlin与Java很好地进行了兼容,但使用不当仍然会遇到不少问题。
1. 问题:无法代理default方法
我们定一个包含default方法的Java接口及其实现类
//Base.java
public interface Base {
default void print() {
System.out.println("Base");
}
}
//BaseImpl.java
class BaseImpl implements Base {
@Override
public void print() {
System.out.println("BaseImpl");
}
}
此接口如果用做koltlin的类代理中:
//Derived.kt
class Derived(b: Base) : Base by b
fun main() {
val b = BaseImpl()
Derived(b).print()
}
输出结果:
虽然代理对象是BaseImpl
,但其print()
方法并没有被重写,依然是执行的default
的方法。
2. 原因分析
by关键字本质上是一个语法糖,帮你省略代理模式所需的模板代码,但是java接口的default方法不会被代理
kt反编译后的java代码可以看到,并没有实现print()方法
3. Kotlin没有default方法
使用kotlin定义接口Base,其他保持不变
//Base.kt
interface Base {
fun print() {
print("Base")
}
}
此时反编译
可以返现,print()
方法被正常代理了
Kotlin接口虽然可以定义方法体实现,但是实现机制与java的default方法实现机制不同,Kotlin中也没有default
关键字
Base通过DefaultImpls
静态类添加默认实现,其子类可以通过super调用实现
Kotlin子类
Kotlin中定义BaseImpl
class BaseImpl : Base {
override fun print() {
super.print()
print("BaseImpl")
}
}
Java子类
Kotlin接口中定义的方法体并非真正给你的default方法,所以当Java子类继承Kotlin接口时,仍然要重写print(),否则会报错
Kotlin接口的方法可以添加@JvmDefault
注解,实现java中default关键字的效果:
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-jvm-default/
其中,最后一句话也很明确的说明了无法对default方法进行代理
4. 结论
回到本文最初的问题,我们因该怎么避免呢?
首先,应该尽量避免Kotlin和Java的混编,特别是避免在继承上产生关系,让上帝的归上帝,凯撒的归凯撒
若迫不得已需要像本例一样(在Kotlin中实现一个Java接口),当有default方法时,需要重写default方法,并手动调用代理,不要寄希望于by关键字