kotlin Cloneable 的奇怪行为

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 文件,发现了很有意思的情况:

  1. Bar 直接继承 Cloneable

    此时,Bar 的 clone 方法为:

     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.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() ,可考虑使用拷贝构造函数的方式来实现类似的功能。

posted @ 2018-05-02 00:38 drop * 阅读(...) 评论(...) 编辑 收藏