kotlin Cloneable 的奇怪行为
在使用 kotlin 的 Cloneable 时,发现它表示得很奇怪。如果类直接继承了 Cloneable ,那么它的表现很正常
和 java 的使用差不多,如下:
package demo interface Foo { fun createClone(): Foo fun doSomething() } class Bar: Foo, Cloneable { override fun createClone(): Bar { return this.clone() as Bar } override fun doSomething() { println("Hello, world!") } } fun main(args:Array<String>) { val bar = Bar() bar.doSomething() val barCloned = bar.createClone() barCloned.doSomething() }
这部分代码运行是正常的,但是如果接口 Foo 也继承了 Cloneable ,那么结果就变得很奇怪,代码如下:
package demo interface Foo: Cloneable { fun createClone(): Foo fun doSomething() } class Bar: Foo, Cloneable { override fun createClone(): Bar { return this.clone() as Bar } override fun doSomething() { println("Hello, world!") } } fun main(args:Array<String>) { val bar = Bar() bar.doSomething() val barCloned = bar.createClone() barCloned.doSomething() }
在调用 bar.createClone()
时,会出现异常:
Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Cloneable$DefaultImpls at demo.Foo$DefaultImpls.clone(demo.kt) at demo.Bar.clone(demo.kt:8) at demo.Bar.createClone(demo.kt:10) at demo.DemoKt.main(demo.kt:22) Caused by: java.lang.ClassNotFoundException: java.lang.Cloneable$DefaultImpls at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 4 more
很奇怪,于是使用 jclasslib 分析了生成的 class 文件,发现了很有意思的情况:
Bar 直接继承 Cloneable
此时,Bar 的 clone 方法为:
0 aload_0 1 invokespecial #47 <java/lang/Object.clone> 4 areturn
这时直接调用了 java Object 的 clone 方法
Foo 继承了 Cloneable 时
此时,Bar 的 clone 方法为:
0 aload_0 1 invokestatic #51 <demo/Foo$DefaultImpls.clone> 4 areturn
嗯,这里调用的是 Foo 接口默认实现类,java 8 的接口默认实现,好像也没有毛病,继续看看
Foo$DefaultImpls.clone
0 aload_0 1 checkcast #9 <java/lang/Cloneable> 4 invokestatic #14 <java/lang/Cloneable$DefaultImpls.clone> 7 areturn
好像有点不对了,Cloneable 是很古老的接口了,没有默认实现,jclasslib 也没有找到
Cloneable$DefaultImpls
类。
现在问题找到了, Foo 接口里面继承了 Cloneable ,而 Bar 实现 Foo 接口时,kotlin 编译后的 class 文件都会出现调用 Cloneable$DefaultImpls
的情况,kotlin 在处理接口的默认实现上有问题。
此问题已报告到 jetbrains 的 kotlin 社区,优先级为 Major ,见 KT-24193
按照 effiective java 的说法,java 的 Cloneable 是个很奇怪的接口,与普通的接口的行为不一样,它使用了超过语言机制本身的约定,用于标示对象的调用 clone() 时的行为。
一般情况下,最好避免使用 clone() ,可考虑使用拷贝构造函数的方式来实现类似的功能。
来源:https://www.cnblogs.com/fengyc/p/8978219.html