在使用 kotlin 的 Cloneable 時,發現它表示得很奇怪。若是類直接繼承了 Cloneable ,那麼它的表現很正常
和 java 的使用差很少,以下: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 ,那麼結果就變得很奇怪,代碼以下:ide
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 文件,發現了頗有意思的狀況:this
Bar 直接繼承 Cloneable.net
此時,Bar 的 clone 方法爲:code
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
ssl
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() ,可考慮使用拷貝構造函數的方式來實現相似的功能。