为什么不在重写的 .clone 函数中使用 cast 操作?

18

我了解Java的克隆机制以及不建议使用它的原因。但是假设我仍然想要使用它。

为了方便起见,我想编写一个类Foo的克隆方法,如下所示:

@Override
public Foo clone(){
    Foo f = (Foo)super.clone();
    //Replace mutable fields
    return f;
}

据我所知,这样做应该是安全的。但是,我注意到API中的Cloneable类没有这样做。这是有原因的吗?这样做是否不好?


1
请注意,只有在这样使用时才有用:“Foo foo = ...; Foo bar = foo.clone()”。 - biziclop
3个回答

17

在Java 5之前,协变返回类型根本不被允许,因此 Foo clone() 不会通过编译。 由于很多实现了 Cloneable 接口的类已经很久以前写好了,这可能是大多数情况的原因。

我怀疑较新的类之所以不使用它,只是因为众所周知的 clone() 非常麻烦,当人们很少使用它时,他们甚至不会想到可以进行类型转换。他们会找到一些“如何实现clone()” 的问题,并用老式方式编写代码。


5
"Good old '它太破了,没有人想浪费时间试图修理它'。" - mgarciaisaia

3
Object.clone() 创建一个新对象,其中所有字段的值都与原始对象相同。对于引用类型的字段,这意味着新字段只是原始对象所引用对象的引用。它不会克隆字段。因此,在许多情况下,默认实现会导致共享数据,这可能是不希望发生的。例如,如果 ArrayList.clone() 使用默认实现,你将得到两个共享相同后备数组的 ArrayList 实例。
如果对象的所有字段都是不可变的,那么将 super.clone() 的结果简单地转换为适当的类型可能是个好主意。

1
API对于clone()的具体预期操作并没有明确说明,但它指出:通常,这意味着复制组成被克隆对象的内部“深层结构”的任何可变对象,并使用对这些对象副本的引用替换引用。,因此clone()应该理想情况下返回一个深拷贝。但是理想情况和必然情况不同... - biziclop
如果所有字段都是不可变的,深拷贝和浅拷贝之间没有区别。 - Paul Boddington
3
没错,但这与问题有什么关系呢?我有点迷惑。 :) - biziclop
它回答了这个问题。不直接使用(MyClass) super.clone()的原因是这是一个浅拷贝,而通常需要深拷贝。我已经说过,在深/浅拷贝之间没有区别的情况下可以这样做。 - Paul Boddington
1
哦,我明白了。很好,我以为问题纯粹是关于为什么我们在覆盖clone()时不使用协变返回类型,而不是关于强制转换super.clone()的问题。 - biziclop
显示剩余2条评论

3

嗯,实际上这不是一种糟糕的风格,自从Java 1.5版本发布以来,协变返回类型已作为泛型的一部分引入,因此这样做可以使您的API更易于使用(无需强制转换)。

来源(《Effective Java 第2版》)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接