kotlin Cloneable 的奇怪行爲

kotlin Cloneable 的奇怪行爲

在使用 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

  1. Bar 直接繼承 Cloneable.net

    此時,Bar 的 clone 方法爲:code

    0 aload_0
     1 invokespecial #47 <java/lang/Object.clone>
     4 areturn

    這時直接調用了 java Object 的 clone 方法對象

  2. Foo 繼承了 Cloneable 時繼承

    此時,Bar 的 clone 方法爲:接口

    0 aload_0
     1 invokestatic #51 <demo/Foo$DefaultImpls.clone>
     4 areturn

    嗯,這裏調用的是 Foo 接口默認實現類,java 8 的接口默認實現,好像也沒有毛病,繼續看看 Foo$DefaultImpls.clonessl

    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() ,可考慮使用拷貝構造函數的方式來實現相似的功能。

相關文章
相關標籤/搜索