Java中的克隆
我在网上读到一段关于克隆的文章。但我不太明白,有人能解释清楚吗 如果类具有final字段,则不能在clone方法中为这些字段指定值。这会导致正确初始化对象的最终字段时出现问题。如果最后一个字段引用的是对象的某个内部状态,那么克隆对象最终将共享该内部状态,这对于可变对象来说肯定是不正确的 以下链接供参考:Java中的克隆,java,Java,我在网上读到一段关于克隆的文章。但我不太明白,有人能解释清楚吗 如果类具有final字段,则不能在clone方法中为这些字段指定值。这会导致正确初始化对象的最终字段时出现问题。如果最后一个字段引用的是对象的某个内部状态,那么克隆对象最终将共享该内部状态,这对于可变对象来说肯定是不正确的 以下链接供参考: 我也不理解它,除了一个类不能实现一个行为良好的克隆方法,如果它的所有字段都没有一个行为良好的克隆方法 总之 o.clone()。此后,复制的引用不能更改为最终字段,因此我们有非自愿的别名 不需
我也不理解它,除了一个类不能实现一个行为良好的
克隆
方法,如果它的所有字段都没有一个行为良好的克隆
方法 总之
o.clone()。此后,复制的引用不能更改为最终字段,因此我们有非自愿的别名李>
- 不需要的别名和易变性彼此不协调:如果
发生变化,克隆也会不自觉地发生变化李>o
- 中的语句是从中复制的,这用良好的代码示例证明了这些语句
- 不需要的别名和易变性彼此不协调:如果
super.clone()
)实现的clone()
),这样最终调用Object.clone()
,从而通过本机代码生成被克隆对象的平面内存副本
假设我们有以下示例(来自):
及
那么person2和person1对场脑的记忆部分是相同的,也就是说,两个人对同一个大脑都有相同的引用。然后,由于Person对象是可变的,因此它们可以学习一些东西:
person1.learn(dvorakTyping);
然后神奇的是,person2也可以在dvorak键盘上打字。对于不可变对象,这个问题不会发生(尽管克隆仍然有问题,因为最终字段仍然无法通过参数初始化,就像在构造函数中一样)
通过构造函数调用克隆
我的前半句话的原因是:您可以通过调用对象的一个构造函数来实现克隆。有些人声称这违反了克隆人的合同,但事实并非如此。下面是一个关于为什么在克隆中调用构造函数的示例(一个主要原因是那些最终字段)
更新
在阅读Hemal对mre答案的评论时,我确实浏览了一眼博客文章中引用的问题,结果发现,该文章抄袭了一些句子,但没有很好的代码示例。LOL.我不推荐使用它,但可以使用sun.misc.Unsafe覆盖最终字段的值。 强烈建议不要使用这个类,但这篇文章并不是关于这个()的。 覆盖最终字段值的示例代码:
public class FinalClone
implements Cloneable {
private final FinalClone finalField;
public FinalClone(FinalClone finalField) {
this.finalField = finalField;
}
@Override
protected FinalClone clone()
throws CloneNotSupportedException {
final FinalClone result = (FinalClone) super.clone();
if (finalField == null) {
return result; // no need to clone null
}
final Field unsafeField;
try {
unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
}
catch (NoSuchFieldException e) {
throw new AssertionError(e);
}
unsafeField.setAccessible(true);
final Unsafe unsafe;
try {
unsafe = (Unsafe) unsafeField.get(null);
}
catch (IllegalAccessException e) {
throw new SecurityException(e);
}
// Update final field
try {
unsafe.putObjectVolatile(
result,
unsafe.objectFieldOffset(
FinalClone.class.getDeclaredField("finalField")),
finalField.clone());
}
catch (NoSuchFieldException e) {
throw new AssertionError(e);
}
return result;
}
}
克隆最终字段没有任何问题,它与其他字段一样有效,但并非始终有效 有时它会成为可变对象的问题 默认情况下,当我们使用克隆时,它会提供浅拷贝(对同一对象的引用),因此在覆盖克隆时,我们会尝试深度复制所有可变对象。当我们尝试深度复制最终字段时,会出现问题,因为(最终字段的)最终引用无法重新分配给新对象
public class Person implements Cloneable
{
private final Brain brain;
private int age;
public Person(Brain aBrain, int theAge)
{
brain = aBrain;
age = theAge;
}
public Object clone()
{
try
{
Person another = (Person) super.clone();
// shallow copy made so far. Now we will make it deep
another.brain = (Brain) brain.clone();
//ERROR: you can't set another.brain
return another;
}
catch(CloneNotSupportedException e) {}
//This exception will not occur
}
}
这个例子来自Davefar的另一个回答中提到的同一个链接,该链接没有到达最后一篇文章。请更新。所以基本上你是说memcpy对于clone来说是个糟糕的实现?嗯……是的。是的,memcpy解决方案肯定有它的缺陷。但是它有一个优点(对我来说不太重要),子类不需要重写clone()方法来调用它们自己的构造函数,因为memcpy只需要获取对象——与它来自的子类无关——并使memcpy.agree完全一致——能够使用memcpy是一个很小的好处。这不是像你需要一个用户定义的复制构造函数时的C++规则那样的吗?我不太适合C++,但是我认为他们的复制构造器更接近于在克隆中调用构造函数的广告解决方案。在Java中,我非常喜欢复制和转换构造函数,通常通过clone()或其他方式实现它们。相关:标准反射API中对此类操作有官方支持,因此不需要使用特定实现的非标准内部类。清洁的解决方案会更简单…
public class FinalClone
implements Cloneable {
private final FinalClone finalField;
public FinalClone(FinalClone finalField) {
this.finalField = finalField;
}
@Override
protected FinalClone clone()
throws CloneNotSupportedException {
final FinalClone result = (FinalClone) super.clone();
if (finalField == null) {
return result; // no need to clone null
}
final Field unsafeField;
try {
unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
}
catch (NoSuchFieldException e) {
throw new AssertionError(e);
}
unsafeField.setAccessible(true);
final Unsafe unsafe;
try {
unsafe = (Unsafe) unsafeField.get(null);
}
catch (IllegalAccessException e) {
throw new SecurityException(e);
}
// Update final field
try {
unsafe.putObjectVolatile(
result,
unsafe.objectFieldOffset(
FinalClone.class.getDeclaredField("finalField")),
finalField.clone());
}
catch (NoSuchFieldException e) {
throw new AssertionError(e);
}
return result;
}
}
public class Person implements Cloneable
{
private final Brain brain;
private int age;
public Person(Brain aBrain, int theAge)
{
brain = aBrain;
age = theAge;
}
public Object clone()
{
try
{
Person another = (Person) super.clone();
// shallow copy made so far. Now we will make it deep
another.brain = (Brain) brain.clone();
//ERROR: you can't set another.brain
return another;
}
catch(CloneNotSupportedException e) {}
//This exception will not occur
}
}